使用 AVX 但不使用 AVX2 分别计算多个 64 位位掩码上的每个位位置

2024-07-03

(有关的:如何在 Sandy Bridge 上将一系列整数中的位快速计数到单独的容器中? https://stackoverflow.com/questions/7793997/how-to-quickly-count-bits-into-separate-bins-in-a-series-of-ints-on-sandy-bridge是这个的早期副本,有一些不同的答案。编者注:这里的答案可能更好。

此外,类似问题的 AVX2 版本,有一整行位的许多箱比一个宽得多uint64_t: 改进列总体计数算法 https://stackoverflow.com/questions/58486138/improve-column-population-count-algorithm)


我正在做一个 C 项目,我需要遍历数千万个掩码(ulong 类型(64 位))并更新一个数组(称为target)基于简单规则的 64 个短整数 (uint16):

// for any given mask, do the following loop
for (i = 0; i < 64; i++) {
    if (mask & (1ull << i)) {
        target[i]++
    }
}

问题是我需要在数千万个掩模上执行上述循环,并且我需要在不到一秒的时间内完成。想知道是否有任何方法可以加快速度,例如使用某种代表上述循环的特殊汇编指令。

目前我在ubuntu 14.04(i7-2670QM,支持AVX,不支持AVX2)上使用gcc 4.8.4编译并运行以下代码,大约花费了2秒。希望使其运行时间低于 200 毫秒。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/stat.h>

double getTS() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec + tv.tv_usec / 1000000.0;
}
unsigned int target[64];

int main(int argc, char *argv[]) {
    int i, j;
    unsigned long x = 123;
    unsigned long m = 1;
    char *p = malloc(8 * 10000000);
    if (!p) {
        printf("failed to allocate\n");
        exit(0);
    }
    memset(p, 0xff, 80000000);
    printf("p=%p\n", p);
    unsigned long *pLong = (unsigned long*)p;
    double start = getTS();
    for (j = 0; j < 10000000; j++) {
        m = 1;
        for (i = 0; i < 64; i++) {
            if ((pLong[j] & m) == m) {
                target[i]++;
            }
            m = (m << 1);
        }
    }
    printf("took %f secs\n", getTS() - start);
    return 0;
}

提前致谢!


有关的:

  • 早期的副本有一些替代想法:如何在 Sandy Bridge 上将一系列整数中的位快速计数到单独的容器中? https://stackoverflow.com/questions/7793997/how-to-quickly-count-bits-into-separate-bins-in-a-series-of-ints-on-sandy-bridge.
  • 哈罗德的回答AVX2 列总体计数算法分别针对每个位列 https://stackoverflow.com/questions/58486138/improve-column-population-count-algorithm.
  • 矩阵转置和总体计数 https://stackoverflow.com/questions/51475704/matrix-transpose-and-population-count有一些关于 AVX2 的有用答案,包括基准测试。它使用 32 位块而不是 64 位。

Also: https://github.com/mklarqvist/positional-popcount https://github.com/mklarqvist/positional-popcount具有 SSE 混合、各种 AVX2、各种 AVX512(包括非常适合大型数组的 Harley-Seal)以及用于位置弹出计数的各种其他算法。可能只为了uint16_t,但大多数都可以适应其他字宽。我认为我下面提出的算法就是他们所说的adder_forest.


最好的选择是 SIMD,在 Sandybridge CPU 上使用 AVX1。编译器不够智能,无法为您自动矢量化循环位,即使您无分支地编写它以给它们更好的机会。

不幸的是,它不够智能,无法自动矢量化逐渐加宽和添加的快速版本。


See intel avx2 中是否有 movemask 指令的逆指令? https://stackoverflow.com/q/36488675了解不同大小的位图 -> 矢量解包方法的摘要。 Ext3h 在另一个答案中的建议很好:将位解压缩为比最终计数数组更窄的内容,可以为每条指令提供更多元素。使用 SIMD 字节效率很高,然后您最多可以执行 255 个垂直paddb没有溢出,在解包之前累加到 32 位计数器数组中。

只需要 4x 16 字节__m128i保存所有 64 个向量uint8_t元素,因此这些累加器可以保留在寄存器中,仅在外循环中扩展到 32 位计数器时才添加到内存中。

解包不必按顺序进行: 你可以随时随机播放target[]在最后一次,在累积所有结果之后。

内部循环可以展开以从 64 或 128 位向量加载开始,并使用 4 或 8 种不同的方式解包pshufb (_mm_shuffle_epi8).


更好的策略是逐步扩大

从 2 位累加器开始,然后进行掩码/移位以将其扩展为 4 位。因此,在最内部的循环中,大多数操作都在处理“密集”数据,而不是立即“稀释”太多数据。更高的信息/熵密度意味着每条指令做了更多有用的工作。

Using SWAR https://en.wikipedia.org/wiki/SWAR在标量或 SIMD 寄存器内进行 32x 2 位加法的技术很简单/便宜,因为无论如何我们都需要避免执行元素顶部的可能性。使用正确的 SIMD,我们会丢失这些计数,而使用 SWAR,我们会破坏下一个元素。

uint64_t x = *(input++);        // load a new bitmask
const uint64_t even_1bits = 0x5555555555555555;  // 0b...01010101;

uint64_t lo = x & even_1bits;
uint64_t hi = (x>>1) & even_1bits;            // or use ANDN before shifting to avoid a MOV copy

accum2_lo += lo;   // can do up to 3 iterations of this without overflow
accum2_hi += hi;   // because a 2-bit integer overflows at 4

然后你重复最多 4 个 4 位元素的向量,然后是 8 个 8 位元素的向量,然后你应该一直扩大到 32 并累积到内存中的数组中,因为无论如何你都会用完寄存器,这外层循环工作并不频繁,因此我们不需要费心去使用 16 位。 (特别是如果我们手动矢量化)。

最大的缺点:这个doesn't自动矢量化,与@njuffa 的版本不同。但与gcc -O3 -march=sandybridge对于AVX1(然后在Skylake上运行代码),这个运行标量64位实际上仍然稍微faster比 @njuffa 代码中的 128 位 AVX 自动矢量化 asm 更好。

但这是 Skylake 上的时序,它有 4 个标量 ALU 端口(和 mov-elimination),而 Sandybridge 缺乏 mov-elimination 并且只有 3 个 ALU 端口,因此标量代码可能会遇到后端执行端口瓶颈。 (但是 SIMD 代码可能几乎一样快,因为有大量的 AND / ADD 与移位混合在一起,并且 SnB 的所有 3 个端口上确实都有 SIMD 执行单元,这些端口上有 ALU。Haswell 刚刚添加了端口 6,用于标量-仅包括轮班和分行。)

通过良好的手动矢量化,速度应该提高近 2 或 4 倍。

但是,如果您必须在这个标量或带有 AVX2 自动矢量化的 @njuffa 之间进行选择,@njuffa 在 Skylake 上速度更快-march=native

如果可以/需要在 32 位目标上构建,这会受到很大影响(由于在 32 位寄存器中使用 uint64_t,因此没有矢量化),而矢量化代码几乎不会受到任何影响(因为所有工作都发生在相同的向量寄存器中)宽度)。

// TODO: put the target[] re-ordering somewhere
// TODO: cleanup for N not a multiple of 3*4*21 = 252
// TODO: manual vectorize with __m128i, __m256i, and/or __m512i

void sum_gradual_widen (const uint64_t *restrict input, unsigned int *restrict target, size_t length)
{
    const uint64_t *endp = input + length - 3*4*21;     // 252 masks per outer iteration
    while(input <= endp) {
        uint64_t accum8[8] = {0};     // 8-bit accumulators
        for (int k=0 ; k<21 ; k++) {
            uint64_t accum4[4] = {0};  // 4-bit accumulators can hold counts up to 15.  We use 4*3=12
            for(int j=0 ; j<4 ; j++){
                uint64_t accum2_lo=0, accum2_hi=0;
                for(int i=0 ; i<3 ; i++) {  // the compiler should fully unroll this
                    uint64_t x = *input++;    // load a new bitmask
                    const uint64_t even_1bits = 0x5555555555555555;
                    uint64_t lo = x & even_1bits; // 0b...01010101;
                    uint64_t hi = (x>>1) & even_1bits;  // or use ANDN before shifting to avoid a MOV copy
                    accum2_lo += lo;
                    accum2_hi += hi;   // can do up to 3 iterations of this without overflow
                }

                const uint64_t even_2bits = 0x3333333333333333;
                accum4[0] +=  accum2_lo       & even_2bits;  // 0b...001100110011;   // same constant 4 times, because we shift *first*
                accum4[1] += (accum2_lo >> 2) & even_2bits;
                accum4[2] +=  accum2_hi       & even_2bits;
                accum4[3] += (accum2_hi >> 2) & even_2bits;
            }
            for (int i = 0 ; i<4 ; i++) {
                accum8[i*2 + 0] +=   accum4[i] & 0x0f0f0f0f0f0f0f0f;
                accum8[i*2 + 1] +=  (accum4[i] >> 4) & 0x0f0f0f0f0f0f0f0f;
            }
        }

        // char* can safely alias anything.
        unsigned char *narrow = (uint8_t*) accum8;
        for (int i=0 ; i<64 ; i++){
            target[i] += narrow[i];
        }
    }
    /* target[0] = bit 0
     * target[1] = bit 8
     * ...
     * target[8] = bit 1
     * target[9] = bit 9
     * ...
     */
    // TODO: 8x8 transpose
}

我们不关心顺序,所以accum4[0]例如,每第 4 位就有 4 位累加器。最后需要的最终修复(但尚未实现)是 8x8 转置uint32_t target[64] array,这可以使用 unpck 和vshufps仅使用 AVX1。 (使用 AVX/AVX2 转置 8x8 浮点 https://stackoverflow.com/questions/25622745/transpose-an-8x8-float-using-avx-avx2)。还有最后 251 个掩模的清理循环。

我们可以使用任何 SIMD 元素宽度来实现这些移位;无论如何,我们必须屏蔽低于 16 位的宽度(SSE/AVX 没有字节粒度移位,最小只有 16 位。)

Arch Linux i7-6700k 的基准测试结果来自 @njuffa 的测试工具,添加了这个。 (Godbolt https://godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAKxAEZSAbAQwDtRkBSAJgCFufSAZ1QBXYskwgA5AHoZAagQECAB0Eg5ggk2QBrVADdMxAGYNUAdwB0aALYyAjiMxa8qFoJkBWLwAYAHLReXF4yaCIsBAC0mDoIUQBGhFEqqIKEbixRgpgqTMRMBJgMAJ5RhsZRtqxlAGwALInJSQTVgrqCURaE8UwGAB6JItEsqNFM3n6BtVwA7NwAzD4BtDPzvgCCi3gsyAwiWPIcCwDCWvioVgjH2Byb27v7h8dnBOgMeAlXN3dbXAs7PYHTBHU7nHYEb4LW73f6Ap4gl5aYg7YBQmFbWEAkzyLAmHaYdAQAD6AHUAJIAOQWXAAlL9tjiwGA8QSiRTqVxiQAZbAbSnE/kAEWJAFk%2BZT6VjWSwQRyaTyJYLKSLxfyGf9MCx8CYNQDHsDQSdutrLIJ0b8dh9Zb90KIEgwQTk0Nr5BADKg8OgpVtZnxNvJA/JuRsAEoAcWwxKpABVsJHQ/ICMd/Rsg/ItIU8MhcfbHfJUKgTMRMA4U7905mCNn5BD5MgEJg9ISAGIkAASeGACFDLhjeFsxnLAaDVZrPAA8hPuYomIJO93e4J%2B4PiMPMWmg3gcRBmQ2m7pWx2uz2%2BwPjLSjn6K%2Bmgwg5wvT8vz8RQUL5ABFZzEEoABWMJgkNUuyYC2JZOFqyAlG63C1AQ9ILKmt5BoWxalm%2B8i0FYvjyAoEB2iIDqYLSkJfkw6C/vkyaITeyH7s26BtsQj5LiuxgYbQ67phwsxCrRtY7ve84nqxL6XjxSHIZ%2B35/gBQGsBIJyiJE7EQLB8FcVJJYEGILBugRREkVYZEUVR8gAFQFkW4GaYGPHvsUORXpJyHabp%2Bl5sR4aYAQ/Z6EpEQEBAl6WVhviYFECy2VefH3LxerFNuuKYPispEsSxLWiI/QZeJswnDxJzJalhIkoKv6/ryuV6vChpIm8eIWlitXPGCJSeNWg5NRsVoErankZk27joG6Hpej6En8ciIjIAQSbngYTAMEmBjRcAPmdZgRboEwJRqVwcEGKQ8iUgAqty3IIS58hucQen4Z5JEGFYBAGMSzpHLwHmEY6T0vW9IgfaF2ERbU672QlDA5AlxDECQ8gRIIIgqKkxBFCNKjMAQgHELYCXatuDKbDI5k3mBqC2CA8jragRrANUxDmlYVgAAL00weAMDYFM/COlKYBYgjAHDyPqBmyB4FY1QEAgpB2CoVjMGwNhyxTCtK2iOMEAULA3gAyoRVBNgQVMNE0c0ANLknrevyKGlLhoIN5CoUkjyHrhTHVw/jyC2mAJPIXC%2BL4ACc8i%2BPUIALL4ID1D7UQBMHbq/nrMYTSOMYIHggjyGbLTyFbNt2w7s45zLJYgvL7hagQgjHbEDbyLK%2BSlDewCFiNWqiN2Sa0zkxBGPIS3V1Y6aZ5XavV5EOf5JIN6iiIDDVpjZSkj0UQnPkP5uqKpInLSx0qMYbgjRAXAcF42C0FwnG8Ofl%2B1AsUS0D6gYABokIIWcmHNEBv3r7YD7yCPiiDAgcL7YDNpxEcSk2DEGcJEPAS03QnAdkAkBJ9wEP3qL8cyMhfhjhzCICEDRiRzV0NnQQpD%2BgcX%2BPULwtRZj%2BBDqHfwsxaheHqDSWg51uTrkIQjEh9QyHyAoYIKhwiczHHfNfBY9DaiyPkYohhvD%2BHaGrEQoRIixESOJNBaR8gFgzC4Y/YxRijFcBMaomimwBHEMiKQ8hlDSEAC8OK%2BFqAo%2BoIcb6zHYbQQIvhrGpjsVopx4jHHg3%2BDKEEu8TgNEDBAHRjiMJJOccImhLwXjyC8P4cSX1kmSOOhfAqI4pJSUKcSKRCx3xpIiRk0EtxoS5yAZUzJvADGVIIMUrwpTNzlOQpUqRHSalujaUaUR6SyGtKmf0Ca0SUoEnkP/dsCSxlTP0ZfAxdTdH6NONk2gCwZn1L0UcLZoydmkL2U07AmFZhAJKfxAZQZKmbKabU15RpslcNpPMhYMT5CoPDGsy5wi3EGNqCwyFkKPEh14RZSZJzwVfWUYw3hfyAWFz1gkiAcSEn8GWQA/FX0gUNHTn8f5izZQnWeRAWgwcGVJzwgscy9RzI3xCoY1l7KX6BjkPIEOgqQ7%2BAaHqAFPByQxltshCAZKxVUpBDwbkE4TgW2JHrckAAtW5Z8fDkpJvWZGO0ihJkbPWZSc0iyYXNjnHY8gG4IHkPnVI6RqzuHkDjBGKhe6Bx8E3EQtgEjGBzng34Y0RpI1sMSB0qA9BuhdFoQRDjhFzXMiobk7hgDHURl2NKtZIgWW0MQda3T81zXMMdOsWcJrXjKWW%2BQVAqDHV0M23Q0V7EEBSQkEoRRiTdByO9ANZyeASqlTIfwF93wGIkr4CGNj%2BkevhtQKgGFzApgbSu7JWd12Nv4PwPK110wdpSXNAx6bM0Xx4LurwfF53lM9UkjCs7EKiImaOvW4711to6bwA9TypLdt7f2zAg68ZeB4G2m9n0%2BCjNPQdcO/RfD0uQ0h1DKHOJ3ueUmRpPxRkYcPUGCGdaiMLoNToZAAbF6uydT2p0AbbWRFpkWktFl8F1ofboJ9X632So/ROl936%2BC/ucv%2BwMHHOMGOfTwUREnTjyHfV%2BiTIz3x5JE3W8pzGfKXt0Ep6TkGp3KbdIBkDwHQPacnTh5pOn8m1AQyYXUmHkIke4vFOKsUNzhozAG4kwtyIiCWn2r0Wp43uETcelNFkSzImzHNHYKhhjZo8Lmwk9bzJRe1jFpM%2BQS3HXSC4kD5atTABljW1MdwQ4JrmuFkR5ktToG9QYuLwxoPyEdGwGW8gohcrZTfYcIdyv8pCFweQbQOjAPYqIIor5CDGCzO4ImIcLBZ0dBAJrp79mjLqyoP99xQ7leq3NcjAaJ3gZO1O0ZM653SfTPy/w5sh7IAo7YKjRBGYLfKw%2Busbaak4S/S8G%2BoI9N7uE5NXb/W9uhwOw9p7ODwOw/O1Ov0s74ovtwgoRo%2BcjvPaxp/esrBFCoAYCNcI08vU%2BqCKPeQpIQSAxBGyqKNTr7vYhyHHGq2C1UGkb9l9nPTj1EBw24HPB6S1sxOD8X%2B2wnQ4DVyNdP3jpY65Nun74MWflY%2ByQdnsWucC7wC8BYuuhcHr5QoGWE9bAqE5uxL%2BogicesXqUBGLA4YMGWjLbOzOJcs6h5k0Z5k1tC/XUGfl5hyJDybgLJ1hBRue/VyzyrSbO0RcwEYFgxJaAtBzpJ/oPhc95/zz4VXXvi9Q/MBhdptmU9anT5n9d/LfBfGZmhtDRe48l6l1nVJ/RcNXxs/a1PNfCBO1R/y%2BGtP5DCkpE6lKJAnTf2rGwH1fRPQjSYPIUUE4ABq5qVAlFj2r0OivMq0z3aMuXZWD/F6P530/75lfXbR3jvSdoydEEMbWKbc2PAFhxO7nO3QZZJsCwjBTBzALB98JdXMNw2828E8ocq808uBM8n1%2BgFg0D0CMDMCGcL9i9yssdYceBZ0oNb9Awj8y8nN4MEDiQkCh8g969G9sIkN6UmDg5aB8Ng8FBBAmBBxzUPBtAC1%2BdNo65p9kAmBx8LA59txU18RGYCBSYwcYDD9HsA0CDOJiDDMIAyCT9oQbhA4%2B8qCaDa5W9L98DL174DMYN3xpdbAlc8ApJYJ%2B9q9DDh8cDFCQ5TDwMop1DLC3Rr87Ce89DPpK8B9nDjDID3M3DPsC1/DRludpM9c%2BdDcf1hc1Mxc3CscTseA8B2UWsiCLCDFSDlDbACC9d1DbNfBEMTBfAqiajqi6iaiwi28MjL1sjhsCU1D8jRlfCiiSiLMAj6g%2B8Kj6jhjaiGj503CSMYCoDPd%2BUGx8hLJRC9IuCTBihoIlokEZ4WAe0s5lZPcc1gA805jXxzIWAt5LBUkO1/AyFzJLxmjxiWcojtcftDdTh8UX09dkiRdXCWdNNkxwNSjOj3xTjYZLAWjJ1Gjpi9tISQ4DVfjL08iMJ85Z0wcEU4TwMOjETCB5AJ0UTLJmYrAFtUTsstNTs%2BjRl85oExciTi0SSeB%2BtiDySsTwcqS8TmZCTQ03MiYNw6xqg7V3QV9St%2BJ9i806xfjiQSwcRL0FMGTEdCCrshSksDiUtRTiTT1wNpSEdnJkdb1rphTlSC1fi2j1TeMyTZTtTVcF0oc00M02Bopz1F8DFqhXdY03Q8stoTAIBrTM1OVJRookpdwwB7TgAdsF10wVAURIgPTuAuATAOZHQRo38lpzBRCigSldYuA6Rop0xbo9JsA35JViQWwNhyRuRTpQwmlrpnMgxwyIQoyMyVBpFuAvAGzel0yuBD4bTgyLSbtLIdgsTjU18OSF1HiG0uMecJlKQd0jdUjykgzL1OcZSC5rZsUcFHMYouSezzVAoo9a4MwwDHchz0wDJ8xMw0ZxSUoMJnRhpgpooRzN1Yid0JypzPjUjGRMITd7V%2BhtYdBy1zj858ASxZpW51Mgw9SRpzBF8ILgAkwLcMI5zwMFydTRNF1Xwtdawdd10Yiip30kihMUiJIOD5AlsrcIBWhtsty7dbBs5wzCQYtjoEhmt24wFPVBA8hGYQQBzkL0wxSJSwTvCDEyLYJ8MuKgwyLLN3lMIszCNpjNQPgcQcJ%2BUEhCxloohdC6wv4pDURw8Y8QLAwodbAOIpKxMl06wYj3wpNawJlRV3jpzQdQypIeKUo%2BK%2BIND4Kr0%2Bj4NbBLxmRJMjLbwDLtkAqNsioX4/LJjZLoZdLE8UkgqZE/KRyzLw5MKeMpVMLbLRcsMBI3QIA3LEKgiRtvKwBJMQzMruLVTzzdR/jwTki/KXN3NMrYq3QgqCo5NQq1y6r8YdR%2BIqzAxjyQQJSyFzxLyhptRgpOsMwi0CAKqLT%2BUgw%2BqPU5wprNoudjpfN0B/MGBAssA09lqVc1zZiLUdyc5YytB%2BJ5rTz1t3wrzRqrp%2BI6xedroH17zzLHzskp8FAlUVU1UNVtVny8KSqpJI1o1kzOMcrOzjpfjjoV1LJPrVV1UtVsBjpqB%2BBeUYblU4afrsBbriMoDKxvMY040wbM0IbVSoaEVYbvqEbjpfS1z5qTqlqhqDFrqiRLwusLruzCL6JOMlsfJGxXwotF5Ysc40BYZjZ%2BIEqxz4iUq9Y0qXy7Lyl/S0SsiLMfLYNyreKqqb0Qy28TAazIzXS3hjAyBPoYzFqqYjb4YWZPovB0AqYotGyQgRAbonKakmyRA0zozK0SaaS/jlab1vaS1prNahRsb7K7JcbpKIi60LrhqXQWbooga1qNqtqtQia2AA6fJ2yTpQ6jyBok6AtuhtrBqeCmaRqWbxr2b7jSMFjGw40eazd%2BaXBBbaxhaSBALU02NhyTLojJbLLskFMbK5aMqpJFbVT74/b3xVb3xHLKqJ7tbQ50xdaIzsYDasBYZikMz86GBzaQTXwramzbbnbh8XKvAnbeLXbHaPaMyvassfbx6ASM6pqNa56wqI7w6o7q7gFl7p9dgEAGZOMBal4Q1O6wzl66yuAJSqYmycRnRhD6aoGQgYGmxhCt6EGvAkHkAnZWzPbnaTBi7MBjp6b8HVqCh1qC6gsdrzwc6gwcz5A8yCy9ZToTgThsAbZwYoCpAD5t6pAvApBSAWBpBfA%2BHUBpACpkiMxRBxBER/haA%2BGTYpAhHflSBdAQBLErAfFQ4jE2DH5/Ag4Fh/BGBpB6g%2BGBGFHhHpA%2BH1BfBSB5HFHSA4BYAkB5YrcyAKAIBnHHRiAQBsxkBaAQ56B8Ql5g1KAEhBG%2BGkhgSShpBZHVZbBBxIgJwWBSgwnSAsBgJgBHQUmALjY8AjB1AzHSBMB%2BgmxhhJApAYmIRigUmPgEgCgfwlIsBom5GURKZynOHGBWB2AhdGBPh1BIAD5UAVA3UPBpAoh%2Bgcwqh8gGxpEuDtQShamvR1pOsJwDcoh7NFrJmZZLHJGJA6AuHpBeH%2BGUmRGpB%2BgRUogEkfHMIQ5sIrBaA3RcBCB4ZFh6BAU1YXHPoFheUxG8K5GwmlHGxyJjBKAD4VGQhsJZgoV9H/AtHIXPFDGpBjGjmCmTnLGQBrHbH2mHHEAUB3nPHyBKAPHgXgAYXSBAmpt1AIBQmCmIn8gom2nYn4mCBEnkmCm0nOnMm2W8B27cmXAUmimSmigmnyAVJuGhGenam6WGmymYmMtWnZGuHOmUBumam%2BngpSBBnhn8mxmJmGZpmalZn0B5mUR0AlmogVnOt1mtBNmEBtmxBdmX4EXDnTHxWTmznagLn%2BdgBHtsSrADcIBHnXtPnXmlILcPnFg6RAUhc/mzGlGwX6h1GhUk3k2Q5agEWkWXXzGpA0WMX/n7GYAcWiXXHCW8XgW9hOnZhg4yXOYKWQmUnaWfxhW7AmWWX6XxX2W2BOX23uWcm8n%2BXimKMhWGXKmxW%2BGam6mShpXhW5WmnFW2BlXkiJW1WBmhnMhtXxnOs9XrgDXWAjWFnTWQRzXVmrXohpZbWhAdnJBHXuHnXjnpB3XPX6wNZ5BZhbmcIA38Ag2XnjpQ3LdPHPmjko3kiY3FHQXVGvB02TG73s2hB0WbH/n9mpAuAoOUWLH4PY2D4QD0h3BY4gA) N = (10000000 / (3*4*21) * 3*4*21) = 9999864(即 10000000 向下舍入为 252 次迭代“展开”因子的倍数,因此我的简单实现是执行相同的工作量,不计算重新排序target[]它不这样做,所以它确实打印不匹配的结果。 但打印的计数与参考数组的另一个位置匹配。)

我连续运行该程序 4 次(以确保 CPU 预热到最大睿频),其中一次运行看起来不错(3 次都没有异常高)。

参考:最佳位循环(下一节)
快:@njuffa 的代码。 (使用 128 位 AVX 整数指令自动矢量化)。
渐进:我的版本(不是由 gcc 或 clang 自动矢量化,至少不是在内部循环中。)gcc 和 clang 完全展开内部 12 次迭代。

  • gcc8.2 -O3 -march=sandybridge -fpie -no-pie
    参考:0.331373 秒,快速:0.011387 秒,渐进:0.009966 秒
  • gcc8.2 -O3 -march=sandybridge -fno-pie -no-pie
    参考:0.397175 秒,快速:0.011255 秒,渐进:0.010018 秒
  • clang7.0 -O3 -march=sandybridge -fpie -no-pie
    参考:0.352381 秒,快速:0.011926 秒,渐进:0.009269 秒(端口 7 uop 的计数非常低,clang 使用索引寻址进行存储)
  • clang7.0 -O3 -march=sandybridge -fno-pie -no-pie
    参考:0.293014 秒,快:0.011777 秒,渐变:0.009235 秒

-三月=天湖(允许 AVX2 用于 256 位整数向量)对两者都有帮助,但 @njuffa 的帮助最大,因为它的更多向量化(包括其最内部的循环):

  • gcc8.2 -O3 -march=skylake -fpie -no-pie
    参考:0.328725秒,快速:0.007621秒,渐进:0.010054秒(gcc显示“渐进”没有增益,只有“快速”)
  • gcc8.2 -O3 -march=skylake -fno-pie -no-pie
    参考:0.333922 秒,快速:0.007620 秒,渐进:0.009866 秒

  • clang7.0 -O3 -march=skylake -fpie -no-pie
    参考:0.260616 秒,快速:0.007521 秒,渐进:0.008535 秒(不知道为什么渐进比 -march=sandybridge 更快;它没有使用 BMI1andn。我猜是因为它使用 256 位 AVX2 作为 k=0..20 外循环vpaddq)

  • clang7.0 -O3 -march=skylake -fno-pie -no-pie
    参考:0.259159 秒, 快:0.007496 秒,渐变:0.008671 秒

没有 AVX,只需 SSE4.2: (-march=nehalem),奇怪的是 clang 的渐变速度比 AVX/tune=sandybridge 更快。 “快”仅比 AVX 慢一点点。

  • gcc8.2 -O3 -march=skylake -fno-pie -no-pie
    参考:0.337178 秒,快速:0.011983 秒,渐进:0.010587 秒
  • clang7.0 -O3 -march=skylake -fno-pie -no-pie
    参考:0.293555 秒,快:0.012549 秒,渐变:0.008697 秒

-fprofile-generate / -fprofile-use为 GCC 提供一些帮助,特别是对于默认情况下根本不展开的“ref”版本。

我强调了最好的,但它们通常都在彼此的测量噪声容限内。这并不奇怪-fno-pie -no-pie有时更快:索引静态数组[disp32 + reg] is not索引寻址模式,只需基址 + disp32,因此它不会在 Sandybridge 系列 CPU 上分层。

但有时用 gcc-fpie更快;我没有检查,但我认为当 32 位绝对寻址成为可能时,gcc 只是搬起了石头砸自己的脚。或者只是代码生成中看似无辜的差异恰好导致了对齐或 uop 缓存问题;我没有详细检查。


对于 SIMD,我们可以简单地执行 2 或 4xuint64_t并行地,仅在最后一步中水平累加,将字节扩展为 32 位元素。(也许通过在车道上洗牌然后使用pmaddubsw乘数为_mm256_set1_epi8(1)将水平字节对添加到 16 位元素中。)

TODO:手动矢量化__m128i and __m256i (and __m512i)的版本。应比上述“渐进”时间快接近 2 倍、4 倍甚至 8 倍。硬件预取可能仍然可以跟上它,除了数据来自 DRAM 的 AVX512 版本,特别是在存在来自其他线程的争用的情况下。我们阅读的每个 qword 都做了大量的工作。


过时的代码:对位循环的改进

您的便携式标量版本也可以改进,将其速度从约 1.92 秒(总体分支误预测率为 34%,快速循环被注释掉!)到〜0.35秒(clang7.0 -O3 -march=sandybridge)在 3.9GHz Skylake 上具有适当的随机输入。或者 1.83 秒的分支版本!= 0代替== m,因为编译器无法证明m始终恰好设置 1 位并/或进行相应优化。

(相对于 @njuffa 或我上面的快速版本为 0.01 秒,所以这在绝对意义上是相当无用的,但作为何时使用无分支代码的一般优化示例值得一提。)

如果您期望零和一的随机组合,那么您需要一些不会错误预测的无分支的东西。正在做+= 0for 零元素避免了这种情况,并且也意味着 C 抽象机肯定会触及该内存,而不管数据如何。

编译器不允许发明写入,所以如果他们想自动矢量化你的if() target[i]++版本,他们必须使用像 x86 这样的屏蔽存储vmaskmovps避免对未修改的元素进行非原子读取/重写target。因此,一些假设的未来编译器可以自动向量化普通标量代码,这样会更容易。

无论如何,一种写法是target[i] += (pLong[j] & m != 0);,使用 bool->int 转换得到一个 0 / 1 整数。

但是,如果我们只是移动数据并隔离低位,那么对于 x86(可能对于大多数其他架构)我们会得到更好的 asm&1。编译器有点愚蠢,似乎没有发现这种优化。他们很好地优化了额外的循环计数器,并把m <<= 1 into add same,same有效地左移,但他们仍然使用异或零/test / setne创建一个 0 / 1 整数。

像这样的内部循环编译效率稍高(但仍然很多很多比我们使用 SSE2 或 AVX 所做的更糟糕,甚至比使用 @chrqlie 的查找表的标量更糟糕,当像这样重复使用时,它会在 L1d 中保持热状态,从而允许 SWARuint64_t):

    for (int j = 0; j < 10000000; j++) {
#if 1  // extract low bit directly
        unsigned long long tmp = pLong[j];
        for (int i=0 ; i<64 ; i++) {   // while(tmp) could mispredict, but good for sparse data
            target[i] += tmp&1;
            tmp >>= 1;
        }
#else // bool -> int shifting a mask
        unsigned long m = 1;
        for (i = 0; i < 64; i++) {
            target[i]+= (pLong[j] & m) != 0;
            m = (m << 1);
        }
#endif

注意unsigned long不保证是 64 位类型,并且不在 x86-64 System V x32(64 位模式下的 ILP32)和 Windows x64 中。或者在 32 位 ABI 中,例如 i386 System V。

已编译在 gcc、clang 和 ICC 的 Godbolt 编译器浏览器上 https://godbolt.org/#z:OYLghAFBqd5QCxAYwPYBMCmBRdBLAF1QCcAaPECAKxAEZSAbAQwDtRkBSAJgCFufSAZ1QBXYskwgA5NwDMeFsgYisAag6yAwoIL5UAOgQbsHAAwBBOQqUrM6rTvQM8AI0PGzlrvMXK1G7QJiBWB3WRMLK19be20AT0EAegI8AFtMMIivHxt/BwTEnSYCTM9PdFEXBjtgTAIAFQBlCABKdQB2PgtVHtUdYhFkAlUU9IA3JgYRsY0u817VWoJRzFQAM3QmOIhuADYCMdJVADkAVQAZc5bZzwXiOrEWaf0DgH1BTGR1Xme3kQ%2BvolVLRTKCwfpTDdIu0ACKeEQsQR4YAsTDoVQKYYEJjEJYcACsPF2ABYCXDZHNPJjVKkmAoINSccBkEdkAgcaoAFRMmaEsltDidW69BFIlFo1QMVBsVQAD3sMOB3ih816bI5nIADgqaZMpcgIAAOLnAsFm64U4U9PBrVQQMBgTUCoXdBa9TXBFgENY7LhcNZ06rooiqPWoZDFTAEzQsbhcC1zN29TCywgQUwJq0dOGunrpVIfAgQTVHUyytZrI6Gs1gzO51QezE%2BuOajRwrj41v4mNxo5OlULUXI1HoqUyrXnaXAHUQIfi0dTzktVuW%2BsVERVOxFYjDNuLOpNVoD3prEh26lUHWQimqS8BU01688W/8fjOyneG3AnqJIEpoJMEMkqoAA7qoLiEKo%2BD3EMDBxFmg6IsOEpjtOqEjKk2p7pqk5sASPBUGSx5JqexDnl6GJtqY9jPngAQkjRlG8G%2BHSJkmbrYridT4XR%2BKKq%2BsiKgQmF7LQxHsT0wlYeEHiCcC4kLIKObZJgDAfKov7gagqBTAAtMYGIUYICA2ikMpMLqggANYISKSHzsBMqpDqYmrqqJFngyV6zBisSqCSPl0cxvDvrZSacXihK8e2fBycWuHAPhhF8d8uw0m0Dp7k%2BYVus5e4QHlWj3rQdbuW6SllN4mAsPgaxZhV9aNl6zZ%2BkQqBWd8%2BK2gCgjRrGfpHEsh5tLpfScQQpV3A8xBPNl0IwlILSMNI%2BJSKQLDSKYa2oNImivj8whiBI3yyLQa0EJti1LVZIBcMS%2BgAJxcKYD2yLstC0LssiGs933LVIxJrRtUhbaQO1SGtgggKYpAXSDi2kHAsBIGgmF4NUZAUBAqOaujmDECAeDIMgtAPfQazowQ%2BNQxALiXaQEEsDicTSGdpCo%2BkXoAPIsHB9NYLSbDVPzeAwSkYyYFD8OkCmnwiFTrNrZiqn084LjEMzmgYJIUhs0EaSK0tzBsCg%2B0CGrUOQEtqCaik0pS7pspfLptLiEYgmCKw6BxOreDoLUqi6VzsiBxWTA6C7xQIJDojiJIJX/at630%2BDsqGrsukMUTXykxC%2Bi0HauCEGecj0KoWtoxjJ3xuXZvnZdLRLQgmBMFgBOtKQN0dhC7QPaYX2Goab20L3uy7P9gPJ9L4OQ9DsMN4jMCICgqCV/j5CUDjeME8AQ%2BkBTDBU8QNN09LjPM4r7Or5zBA83z0sC6wwDCw/oufOLkv07LyDyzrbPKwwVWrgNbEDiFrLAl99apENowJ%2Bptgrm1cJbDuNs7aImkI7Z2rs2Rtk9jVH2wR/Z2CDiHXSYcI60gINHIQscJB0CNitIGKdpBpwzgxZkXxDT6BDhAIuRAyKlyOBXXGVdS5tD2gg%2Bu8NG6d1uvdB6CjFFKKUePQBAMmHT2kLPGGcMtpLSRsvLeGMN7Y1XiI/GKBjbAHaKCfelNqaUFPqDc%2BoDL4c2qrfXmLMH6YEFs/HWoNoLvzwBLKWoNv6/0vgAoB6tNba0gcEaBusEZWPgXwXgjAkHwGtrbPA9sMFO0Dtg92MI8He19kQwOwdQ4BgoVHGOR144MKkEnYGoNU7p0zsSVQSgn6qHaHnaivD8D8JOmXYR28TqyHEXXee0jrq3XxBPDR7StFCDnroq6/0uArO2mszZMiJbHzyRtYkQA%3D%3D%3D,gcc 循环中的微指令少了 1 个。但所有这些都只是简单的标量,其中 clang 和 ICC 展开为 2。

# clang7.0 -O3 -march=sandybridge
.LBB1_2:                            # =>This Loop Header: Depth=1
   # outer loop loads a uint64 from the src
    mov     rdx, qword ptr [r14 + 8*rbx]
    mov     rsi, -256
.LBB1_3:                            #   Parent Loop BB1_2 Depth=1
                                    # do {
    mov     edi, edx
    and     edi, 1                              # isolate the low bit
    add     dword ptr [rsi + target+256], edi   # and += into target

    mov     edi, edx
    shr     edi
    and     edi, 1                              # isolate the 2nd bit
    add     dword ptr [rsi + target+260], edi

    shr     rdx, 2                              # tmp >>= 2;

    add     rsi, 8
    jne     .LBB1_3                       # } while(offset += 8 != 0);

这比我们得到的要好一些test / setnz。无需展开,bt / setc可能是相等的,但编译器不擅长使用bt实施bool (x & (1ULL << n)), or bts实施x |= 1ULL << n.

如果许多字的最高设置位远低于位 63,则循环while(tmp)可能是一场胜利。如果大多数情况下它只节省约 0 到 4 次迭代,那么分支错误预测就变得不值得,但如果它经常节省 32 次迭代,那确实是值得的。也许在源代码中展开,因此循环仅测试tmp每 2 次迭代(因为编译器不会为您执行该转换),但是循环分支可以是shr rdx, 2 / jnz.

在 Sandybridge 系列上,每 2 位输入前端有 11 个融合域微指令。 (add [mem], reg使用非索引寻址模式微融合加载+ALU和存储地址+存储数据,其他一切都是单微指令。添加/jcc 宏熔断器。请参阅 Agner Fog 的指南,以及https://stackoverflow.com/tags/x86/info https://stackoverflow.com/tags/x86/info)。因此,它应该以每 2 位 3 个周期 = 每 96 个周期 1 个 uint64_t 的速度运行。 (Sandybridge 不会在其循环缓冲区内部“展开”,因此非 4 倍微指令计数基本上会向上舍入,这与 Haswell 及更高版本不同)。

与 gcc 的未展开版本相比,每 1 位 7 个微指令 = 每位 2 个周期。如果你编译的是gcc -O3 -march=native -fprofile-generate/ 测试运行 /gcc -O3 -march=native -fprofile-use,配置文件引导的优化将启用循环展开。

对于完全可预测的数据,这可能比分支版本慢,就像您从memset具有任何重复字节模式。我建议用快速 PRNG(如 SSE2 xorshift+)随机生成的数据填充数组,或者如果您只是对计数循环进行计时,则使用您想要的任何数据,例如rand().

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 AVX 但不使用 AVX2 分别计算多个 64 位位掩码上的每个位位置 的相关文章

  • WPF DataGrid 行标题可见性错误

    我正在使用 DataGrid 来显示多个字段 其中之一是多行描述 网格显示数据很好 直到我尝试通过设置隐藏标题行HeadersVisibility Column 标题行消失 但当我滚动时 某些随机行的行标题会重新出现 我已将其范围缩小到显示
  • 在 C 中搜索已排序的数组

    我正在解决 C 语言中的一个问题 我有一个简短的问题 问题如下 我得到了一些已排序的整数数组 比如说 a i 1 2 3 3 3 现在 我应该运行一个程序来搜索给定的整数 返回第一次出现的位置以及该整数在数组中出现的次数 所以 如果我正在寻
  • 在派生虚函数中强制执行正确的参数类型

    我发现很难非常简洁地描述这个问题 所以我附上了演示程序的代码 总的想法是 我们需要一组派生类 它们被迫从基类实现一些抽象 Foo 函数 每个派生的 Foo 调用都必须接受不同的参数作为输入 但所有参数也应该从 BaseInput 类派生 到
  • 在 C++ 中访问静态类变量?

    复制 C 对静态类成员的未定义引用 https stackoverflow com questions 272900 c undefined reference to static class member 如果我有这样的类 结构 head
  • 如何使用 MVVM light 处理 WP 8.1 上的后退按钮?

    我正在寻找适当的方法来处理 Windows Phone 8 1 WinRT 上的后退按钮按下事件NavigationService在 MVVM light 5 上可用 到目前为止 我认为最好的地方是在ViewModelLocator通过注册
  • 如何检测动态绘制图形的点击?

    我正在面板上绘制文件和文件夹名称列表 并试图集体讨论检测用户是否单击文件 文件夹名称以及何时单击文件 文件夹名称以及他们实际单击的文件或文件夹名称的最佳方法 以下是我迄今为止编写的方法 我的第一个想法是用透明控件搭载每段文本 并以这种方式动
  • 如何知道机器的堆栈在内存中是向上还是向下增长? (JAVA)

    我有一个 C 程序来检查机器堆栈在内存中是向上还是向下增长 事情是这样的 include
  • 当我将鼠标悬停在组合框项目上时引发事件

    当我将鼠标悬停在组合框项目上时 我无法找到要触发的事件 我正在使用 Windows 窗体来构建应用程序 我发现WPF有类似的东西 当我将鼠标悬停在组合框项目上时如何更改标签文本 https stackoverflow com questio
  • EditorFor/CheckBoxFor boolean 将 data-val-required 属性添加到 HTML,而不将 required 属性添加到模型

    我的模型类有一个没有必需属性的 bool 属性 public class Test public bool TestBool1 get set 然后在我的剃刀视图中 我使用 EditorFor CheckBoxFor 也会发生同样的情况 d
  • 在 Xamarin.Forms 中添加方向更改布局

    我需要实现的是 在将屏幕从纵向更改为横向时向现有页面添加布局 我已经成功地使用检测方向变化void OnSizeAllocation 双倍宽度 双倍高度 但我无法为此事件添加布局 我的示例 C 代码是 public class MyLayo
  • 如何(自动)生成 MVC + Entity 的 Web api 控制器或基因查询所有类型

    作为 MVC4 Entity WebAPI 的新手 我对仅仅拥有 GET POST 控制器处理程序和到数据模型的自定义映射感到沮丧 我想我要问的是有一个生成工具 例如从数据类创建控制器类 这样我就可以直接从数据执行简单的 GET 命令 制作
  • 创建新选项卡并管理它们 - Selenium

    这是我的代码 var chromeOptions new ChromeOptions chromeOptions AddArguments headless driver new OpenQA Selenium Chrome ChromeD
  • 使用Swashbuckle ISchemaFilter设置输入参数的默认描述

    我想在文档架构中为所有具有 DateTime 类型的输入参数添加默认描述 这样客户就会了解我们正在使用哪些格式等 我可以为此目的创建 ISchemaFilter 的自定义实现吗 我知道我可以使用 xml 注释添加描述 但在这种情况下 我应该
  • 在 ASP.NET MVC 2.0 中编辑实体框架中的对象并将其保存到数据库中

    所以我知道 EF 实体会跟踪它们自己的更改 并在调用 savechanges 时将它们保存到数据库中 但是这种情况怎么样 我有一个旨在编辑博客文章的页面 它有两种操作方法 HttpGet public ViewResult EditBlog
  • Python 解释器优化

    假设我有一个字符串x Python 解释器是否足够聪明 能够知道 string replace x x 应转换为NOP 我怎样才能找到这个 是否有任何参考资料显示解释器能够根据句法分析动态执行哪种类型的优化 不 Python 不能对 NOP
  • 基于路由参数的子容器注册

    我们有一个多租户 ASP NET MVC 应用程序 为多个客户端托管预订引擎 这些客户端中的每一个都有多个可以影响 Unity 容器配置的包 我们为每个请求创建一个子容器 并根据通过路由传递的客户端和包参数注册不同的接口实现 目前 我们正在
  • 将字符串中的每个字符转换为 ASCII

    谁能告诉我如何轻松地将字符串中的每个字符转换为 ASCII 值 以便我可以对这些值求和 我需要对哈希函数的值求和 字符串中的每个字符都已经是 ascii include
  • 在 EF core 中启用迁移吗?

    我正在开始使用 EF Core 2 0 我有一个针对 NET 4 6 1 的控制台应用程序 我有一个非常简单的模型类 以及这个上下文 public class ContextCore DbContext protected override
  • GO 语句破坏了 .NET 中的 SQL 执行

    我有一个非常简单的 C 命令 shell 应用程序 它执行 SQL Server 生成的 SQL 脚本来编写模式和数据脚本 它在 GO 声明中爆炸了 错误信息 GO 附近的语法不正确 这是完整的 sql 脚本 Object Table gy
  • 在头文件 (.h) 中声明构造函数然后在类文件 (.cpp) 中定义 C++ 的语法 [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 如果有人可以解决这个问题 我将不胜感

随机推荐

  • 如何在 React 中使用 API 进行异步调用?

    所以我正在尝试用 React 来实践 我非常决心写维基百科查看器使用 ReactJS 的项目 然而 昨天在使用 React 组件从维基百科的 API 获取数据后 我遇到了麻烦 我认为这与异步调用有关 我定义了一个常量 它接受查询并将其注入
  • 以编程方式设置 TextView 的样式

    我正在尝试使用TextView构造函数的样式如下 TextView myText new TextView MyActivity this null R style my style 但是 当我这样做时 文本视图似乎没有采用该样式 我通过在
  • 使用 Polly 重试

    这2个重试策略表明相同吗 Policy Handle
  • 从 URL 启动活动

    我试图在用户浏览到某个网址时启动我的应用程序 我找到了一些示例 它们在清单中都有相同的内容 但它对我不起作用 我已将意图过滤器放在活动和接收器下 这是我的清单片段
  • 从 div 到 div 绘制弯曲的 SVG 箭头线

    我想使用 SVG 绘制两条弯曲的箭头线来连接两个元素以指示它们来回移动 如下所示 我读过一些有关 SVG 的内容 但我不完全确定如何创建垂直线 其次 如果SVG需要坐标 我是否必须在创建SVG绘图之前找到元素的坐标位置 调整窗口大小是否需要
  • 没有composer如何使用PHPMailer?

    我想使用最新的PHP邮件程序 https github com PHPMailer PHPMailer图书馆与require once 而不是与 Composer 搞混 我想要一个简单的纯 xcopy 部署 这就是我正在尝试做的事情 req
  • Hibernate Spatial 与 PostGIS 的地理类型

    我刚刚向 Postgres 数据库添加了一个 Geography 类型的列 该数据库由使用 hibernate 和 hibernate Spatial 的应用程序访问 每当我使用 hibernate 访问该列时 都会收到一条错误消息 无法转
  • Rails PostgreSQL 数字字段溢出错误

    我的架构上有一个价格小数字段 每次我尝试插入价格时 pg 都会出现此错误 有人能给我任何启发吗 谢谢 配置 t decimal price precision gt 2 scale gt 2 ERROR PG Error ERROR num
  • Git 和 pbxproj

    我正在查看一个开源 Mac 应用程序 他们为 gitignore 提供了一些建议值 他们正是我所期望的 但是 他们还建议在 gitattributes 文件中输入一个条目 pbxproj crlf diff merge 我不是最了解 git
  • Android Camera.takePicture - 可以禁用快门声音和预览表面吗?

    我正在开发一个应用程序 它允许用户快速单击并忘记快照 除了相机按照我想要的方式工作之外 大部分应用程序都已完成 现在我的相机可以工作 但我似乎找不到禁用快门声音的方法 也找不到禁用显示预览的方法 我能够用控件覆盖预览 但如果可能的话 我宁愿
  • Java GC 提升失败

    我的堆大小为 16G 在 Java 6u43 上 Young Gen 4G Old Gen 12G 我看到了下面的促销失败 有趣的是 在故障发生之前 old gen 大小从 12G 下降到 9G young gen 大小从 3 4G 下降到
  • 为什么WEKA评估类需要训练实例?

    我不明白为什么 Weka 评估类构造函数需要火车实例才能工作 有人能给我解释一下吗 理论上 评估仅取决于经过训练的模型 cls在下一个代码中 和测试数据 测试集 Thanks 这是一个例子 TrainingSet is the traini
  • PHP:如何检查日期是今天、昨天还是明天

    我想检查一个日期是今天 明天 昨天还是其他 但我的代码不起作用 Code timestamp 2014 09 02T13 34 date date d m Y H i match date date d m Y H i strtotime
  • HAXM 硬件要求

    我是否需要升级处理器才能运行 HAXM 或者我错过了什么 如果我错过了什么 任何提示将不胜感激 我的处理器是 Intel Core2 6300 1 86GHz 1 86GHz 安装内存 4 00 Gb 在Win7上运行 我在BIOS中启用了
  • Winforms - 在面板内填充用户控件

    我是 winforms 的新手 我想做的是一个简单的表单 在我的表格中有一个面板 该面板链接到用户控件 用户控件应填充面板 这是我的表格 public Form1 InitializeComponent UserControl1 userC
  • 在 github 操作中找不到测试

    I am trying to do an automated test There should be 21 tests but github actions can t find them for some reason https gi
  • 如何使 Zelle 图形中的分形渲染速度更快?

    仅需要 100 次迭代即可渲染此 Mandelbrot 集需要一个多小时 而渲染 10 000 次迭代则需要 10 小时 有没有办法让它更快 from graphics import width 700 height 700 win Gra
  • 将 COM DLL 与 FSI 结合使用

    在 FSI 中是否有一种方法可以引用和使用已注册的 COM 组件 在正常的 fs 编译程序中 我可以简单地引用有问题的组件 然后打开相关的生成的命名空间 然而 在 fsx 文件中 我似乎无法复制这种行为 我尝试使用 r 直接引用 dll 并
  • SQL Server 复制全表定义

    在 SQL Server 企业版中 可以使用 脚本表为 创建到 为预先存在的表生成创建语句 这会生成包含索引的表的完整创建语句 有没有办法在 T SQL 中执行此操作 从而能够将表定义复制到新表 直接 T SQL 中没有任何东西可以直接使用
  • 使用 AVX 但不使用 AVX2 分别计算多个 64 位位掩码上的每个位位置

    有关的 如何在 Sandy Bridge 上将一系列整数中的位快速计数到单独的容器中 https stackoverflow com questions 7793997 how to quickly count bits into sepa