当前位置: 首页 > 知识库问答 >
问题:

比特数:预处理器魔术与现代C

傅花蜂
2023-03-14

假设我想为16位块中的64位整数创建一个编译时构造的位计数查找表。我知道的唯一方法是使用以下代码:

#define B4(n) n, n + 1, n + 1, n + 2
#define B6(n)   B4(n),   B4(n + 1),   B4(n + 1),  B4(n + 2)  
#define B8(n)   B6(n),   B6(n + 1),   B6(n + 1),  B6(n + 2)
#define B10(n)  B8(n),   B8(n + 1),   B8(n + 1),  B8(n + 2)
#define B12(n)  B10(n),  B10(n + 1),  B10(n + 1), B10(n + 2)
#define B14(n)  B12(n),  B12(n + 1),  B12(n + 1), B12(n + 2)
#define B16(n)  B14(n),  B14(n + 1),  B14(n + 1), B14(n + 2)
#define COUNT_BITS B16(0), B16(1), B16(1), B16(2)

unsigned int lookup[65536] = { COUNT_BITS };

有没有一种现代的(C 11/14)方法可以获得同样的结果?

共有3个答案

孙子民
2023-03-14

使用c 17,您可以使用constexpr在编译时构造查找表。通过人口计数计算,可以按如下方式构建查找表:

#include <array>
#include <cstdint>

template<std::size_t N>
constexpr std::array<std::uint16_t, N> make_lookup() {
    std::array<std::uint16_t, N> table {};

    for(std::size_t i = 0; i < N; ++i) {
        std::uint16_t popcnt = i;

        popcnt = popcnt - ((popcnt >> 1) & 0x5555);
        popcnt = (popcnt & 0x3333) + ((popcnt >> 2) & 0x3333);
        popcnt = ((popcnt + (popcnt >> 4)) & 0x0F0F) * 0x0101;

        table[i] = popcnt >> 8;
    }
    return table;
}

示例用法:

auto lookup = make_lookup<65536>();

自c 17以来,std::array::运算符[]是constepr,对于c 14,上面的示例可以编译,但不是真正的constepr。

如果您想惩罚编译器,也可以使用可变模板初始化生成的std::array。通过使用索引技巧,此版本也将适用于c 14,甚至适用于c 11。

#include <array>
#include <cstdint>
#include <utility>

namespace detail {
constexpr std::uint8_t popcnt_8(std::uint8_t i) {
    i = i - ((i >> 1) & 0x55);
    i = (i & 0x33) + ((i >> 2) & 0x33);
    return ((i + (i >> 4)) & 0x0F);
}

template<std::size_t... I>
constexpr std::array<std::uint8_t, sizeof...(I)>
make_lookup_impl(std::index_sequence<I...>) {
    return { popcnt_8(I)... };
}
} /* detail */

template<std::size_t N>
constexpr decltype(auto) make_lookup() {
    return detail::make_lookup_impl(std::make_index_sequence<N>{});
}

注意:在上面的示例中,我从16位整数切换到8位整数。

装配输出

8位版本只为make\u lookup\u impl函数生成256个模板参数,而不是65536。后者太多,将超过模板实例化深度的最大值。如果您有足够的虚拟内存,可以使用GCC上的编译器标志增加此最大值,并切换回16位整数。

无论如何,看看下面的演示,试试8位版本如何计算64位整数的设定位。

现场演示

养星汉
2023-03-14

这是一个C 14解决方案,基本上是围绕constexpr的用法构建的:

// this struct is a primitive replacement of the std::array that 
// has no 'constexpr reference operator[]' in C++14 
template<int N>
struct lookup_table {
    int table[N];

    constexpr int& operator[](size_t i) { return table[i]; }
    constexpr const int& operator[](size_t i) const { return table[i]; }
};

constexpr int bit_count(int i) { 
    int bits = 0; 
    while (i) { i &= i-1; ++bits; } 
    return bits;
}

template<int N> 
constexpr lookup_table<N> generate() {
    lookup_table<N> table = {};

    for (int i = 0; i < N; ++i)
        table[i] = bit_count(i);

    return table;
}

template<int I> struct Check {
    Check() { std::cout <<  I << "\n"; }
};

constexpr auto table = generate<65536>();

int main() {
    // checks that they are evaluated at compile-time 
    Check<table[5]>();
    Check<table[65535]>();
    return 0;
}

可运行版本:http://ideone.com/zQB86O

卞俊哲
2023-03-14

为什么不使用标准库?

#include <bitset>

int bits_in(std::uint64_t u)
{
    auto bs = std::bitset<64>(u);
    return bs.count();
}

生成的汇编器(使用-O2-mar=本机编译):

bits_in(unsigned long):
        xor     eax, eax
        popcnt  rax, rdi
        ret

在这一点上值得一提的是,并非所有x86处理器都有此指令,因此(至少在gcc中)您需要让它知道要编译的架构。

@tambre提到,在现实中,如果可以的话,乐观主义者会走得更远:

volatile int results[3];

int main()
{
    results[0] = bits_in(255);
    results[1] = bits_in(1023);
    results[2] = bits_in(0x8000800080008000);   
}

生成的汇编程序:

main:
        mov     DWORD PTR results[rip], 8
        xor     eax, eax
        mov     DWORD PTR results[rip+4], 10
        mov     DWORD PTR results[rip+8], 4
        ret

像我这样的老派捣蛋鬼需要找到新的问题来解决:)

并不是每个人都对该解决方案依赖cpu帮助来计算比特数感到高兴。那么,如果我们使用自动生成的表,但允许开发人员配置它的大小呢?(警告-16位表版本的编译时间长)

#include <utility>
#include <cstdint>
#include <array>
#include <numeric>
#include <bitset>


template<std::size_t word_size, std::size_t...Is>
constexpr auto generate(std::integral_constant<std::size_t, word_size>, std::index_sequence<Is...>) {
    struct popcount_type {
        constexpr auto operator()(int i) const {
            int bits = 0;
            while (i) {
                i &= i - 1;
                ++bits;
            }
            return bits;
        }
    };
    constexpr auto popcnt = popcount_type();

    return std::array<int, sizeof...(Is)>
            {
                    {popcnt(Is)...}
            };
}

template<class T>
constexpr auto power2(T x) {
    T result = 1;
    for (T i = 0; i < x; ++i)
        result *= 2;
    return result;
}


template<class TableWord>
struct table {
    static constexpr auto word_size = std::numeric_limits<TableWord>::digits;
    static constexpr auto table_length = power2(word_size);
    using array_type = std::array<int, table_length>;
    static const array_type& get_data() {
        static const array_type data = generate(std::integral_constant<std::size_t, word_size>(),
                                           std::make_index_sequence<table_length>());
        return data;
    };

};

template<class Word>
struct use_table_word {
};

template<class Word, class TableWord = std::uint8_t>
int bits_in(Word val, use_table_word<TableWord> = use_table_word<TableWord>()) {
    constexpr auto table_word_size = std::numeric_limits<TableWord>::digits;

    constexpr auto word_size = std::numeric_limits<Word>::digits;
    constexpr auto times = word_size / table_word_size;
    static_assert(times > 0, "incompatible");

    auto reduce = [val](auto times) {
        return (val >> (table_word_size * times)) & (power2(table_word_size) - 1);
    };

    auto const& data = table<TableWord>::get_data();
    auto result = 0;
    for (int i = 0; i < times; ++i) {
        result += data[reduce(i)];
    }
    return result;
}

volatile int results[3];

#include <iostream>

int main() {
    auto input = std::uint64_t(1023);
    results[0] = bits_in(input);
    results[0] = bits_in(input, use_table_word<std::uint16_t>());

    results[1] = bits_in(0x8000800080008000);
    results[2] = bits_in(34567890);

    for (int i = 0; i < 3; ++i) {
        std::cout << results[i] << std::endl;
    }
    return 0;
}

此版本允许在查找表中使用任意数量的位,并支持任何输入类型,即使其小于查找表中的位数。

如果高比特为零,它也会短路。

#include <utility>
#include <cstdint>
#include <array>
#include <numeric>
#include <algorithm>

namespace detail {
    template<std::size_t bits, typename = void>
    struct smallest_word;

    template<std::size_t bits>
    struct smallest_word<bits, std::enable_if_t<(bits <= 8)>>
    {
        using type = std::uint8_t;
    };

    template<std::size_t bits>
    struct smallest_word<bits, std::enable_if_t<(bits > 8 and bits <= 16)>>
    {
        using type = std::uint16_t;
    };

    template<std::size_t bits>
    struct smallest_word<bits, std::enable_if_t<(bits > 16 and bits <= 32)>>
    {
        using type = std::uint32_t;
    };

    template<std::size_t bits>
    struct smallest_word<bits, std::enable_if_t<(bits > 32 and bits <= 64)>>
    {
        using type = std::uint64_t;
    };
}

template<std::size_t bits> using smallest_word = typename detail::smallest_word<bits>::type;

template<class WordType, std::size_t bits, std::size_t...Is>
constexpr auto generate(std::index_sequence<Is...>) {

    using word_type = WordType;

    struct popcount_type {
        constexpr auto operator()(word_type i) const {
            int result = 0;
            while (i) {
                i &= i - 1;
                ++result;
            }
            return result;
        }
    };
    constexpr auto popcnt = popcount_type();

    return std::array<word_type, sizeof...(Is)>
            {
                    {popcnt(Is)...}
            };
}

template<class T>
constexpr auto power2(T x) {
    return T(1) << x;
}

template<std::size_t word_size>
struct table {

    static constexpr auto table_length = power2(word_size);

    using word_type = smallest_word<word_size>;

    using array_type = std::array<word_type, table_length>;

    static const array_type& get_data() {
        static const array_type data = generate<word_type, word_size>(std::make_index_sequence<table_length>());
        return data;
    };

    template<class Type, std::size_t bits>
    static constexpr auto n_bits() {
        auto result = Type();
        auto b = bits;
        while(b--) {
            result = (result << 1) | Type(1);
        }
        return result;
    };

    template<class Uint>
    int operator()(Uint i) const {
        constexpr auto mask = n_bits<Uint, word_size>();
        return get_data()[i & mask];
    }

};

template<int bits>
struct use_bits {
    static constexpr auto digits = bits;
};

template<class T>
constexpr auto minimum(T x, T y) { return x < y ? x : y; }

template<class Word, class UseBits = use_bits<8>>
int bits_in(Word val, UseBits = UseBits()) {

    using word_type = std::make_unsigned_t<Word>;
    auto uval = static_cast<word_type>(val);


    constexpr auto table_word_size = UseBits::digits;
    constexpr auto word_size = std::numeric_limits<word_type>::digits;

    auto const& mytable = table<table_word_size>();
    int result = 0;
    while (uval)
    {
        result += mytable(uval);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wshift-count-overflow"
                uval >>= minimum(table_word_size, word_size);
#pragma clang diagnostic pop
    }

    return result;
}

volatile int results[4];

#include <iostream>

int main() {
    auto input = std::uint8_t(127);
    results[0] = bits_in(input);
    results[1] = bits_in(input, use_bits<4>());
    results[2] = bits_in(input, use_bits<11>());
    results[3] = bits_in(input, use_bits<15>());

    for (auto&& i : results) {
        std::cout << i << std::endl;
    }

    auto input2 = 0xabcdef;
    results[0] = bits_in(input2);
    results[1] = bits_in(input2, use_bits<4>());
    results[2] = bits_in(input2, use_bits<11>());
    results[3] = bits_in(input2, use_bits<15>());

    for (auto&& i : results) {
        std::cout << i << std::endl;
    }

    auto input3 = -1;
    results[0] = bits_in(input3);
    results[1] = bits_in(input3, use_bits<4>());
    results[2] = bits_in(input3, use_bits<11>());
    results[3] = bits_in(input3, use_bits<15>());

    for (auto&& i : results) {
        std::cout << i << std::endl;
    }

    return 0;
}

输出示例:

7
7
7
7
17
17
17
17
32
32
32
32

调用〈code〉bits\u in(int,use\u bits)的结果程序集输出

.L16:
        mov     edx, edi
        and     edx, 2047
        movzx   edx, WORD PTR table<11ul>::get_data()::data[rdx+rdx]
        add     eax, edx
        shr     edi, 11
        jne     .L16

这在我看来是合理的。

 类似资料:
  • 在 webpack 中,所有的预处理器需要匹配对应的 loader。vue-loader 允许你使用其它 webpack loader 处理 Vue 组件的某一部分。它会根据 lang 属性自动推断出要使用的 loader。 CSS 例如,使用 Sass 编译我们的 <style> 语言块: npm install sass-loader node-sass --save-dev <style

  • 得益于 vue-loader, 我们可以通过 lang 属性在组件中的<template>, <script> 或 <style> 上使用各种预处理器。 举个例子,我们在 pages/index.vue 组件中使用 Pug, CoffeeScript 和 Sass: <template lang="pug"> h1.red Hello {{ name }}! </template> <scr

  • 这个模板已经预设设置大部分流行的css预处理器,包括 LESS, SASS, Stylus, 和 PostCSS。要使用一个预处理器的话 ,所有你需要做的就是安装相应的webpack loader。例如,使用SASS: npm install sass-loader node-sass --save-dev 你需要安装node-sass,因为saas-loader需要这个依赖项 在组件里面使用预

  • 预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。 所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。 我们已经看到,之前所有的实例中都有 #include 指令。这个宏用于把头文件包含到源文件中。 C++ 还支持很多预处理指令,比如 #include、#define、#if、#else、#line

  • 主要内容:1. 预处理器示例,2. 预定义的宏,3. 预处理器运算符,4. 参数化宏Objective-C预处理器不是编译器的一部分,而是编译过程中的一个单独步骤。 简单来说,Objective-C预处理器只是一个文本替换工具,它指示编译器在实际编译之前进行必要的预处理。 我们将Objective-C预处理器称为OCPP。 所有预处理器命令都以井号()开头。它必须是第一个字符(前面不能有空格),并且为了便于阅读,预处理器指令应该从第一列开始。 以下部分列出了所有重要的预处理程序指

  • 1.13.1 魔术方法: 在Python中的面向对象中有很多魔术方法如: __init__: 构造函数,在生成对象时调用 __del__: 析构函数,释放对象时使用 __str__: 使用print(对象)或者str(对象)的时候触发 __repr__: 在使用repr(对象)的时候触发 __setitem__ : 按照索引赋值:每当属性被赋值的时候都会调用该方法:self._