C++ 从64字节数组查找字节的最快方法
我在代码中有以下关键位置:我需要从64字节数组中查找大约1'000'000次 最小代码:C++ 从64字节数组查找字节的最快方法,c++,performance,C++,Performance,我在代码中有以下关键位置:我需要从64字节数组中查找大约1'000'000次 最小代码: #include <iostream> #include <stdint.h> #include <random> #include <chrono> #include <ctime> #define TYPE uint8_t #define n_lookup 64 int main(){ const int n_indices = 10
#include <iostream>
#include <stdint.h>
#include <random>
#include <chrono>
#include <ctime>
#define TYPE uint8_t
#define n_lookup 64
int main(){
const int n_indices = 1000000;
TYPE lookup[n_lookup];
TYPE indices[n_indices];
TYPE result[n_indices];
// preparations
std::default_random_engine generator;
std::uniform_int_distribution<int> distribution(0, n_lookup);
for (int i=0; i < n_indices; i++) indices[i] = distribution(generator);
for (int i=0; i < n_lookup; i++) lookup[i] = distribution(generator);
std::chrono::time_point<std::chrono::system_clock> start = std::chrono::system_clock::now();
// main loop:
for (int i=0; i < n_indices; i++) {
result[i] = lookup[indices[i]];
}
std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end - start;
std::cout << "computation took " << elapsed_seconds.count() * 1e9 / n_indices << " ns per element"<< std::endl;
// printing random numbers to avoid code elimination
std::cout << result[12] << result[45];
return 0;
}
在使用g++lookup.cpp-std=gnu++11-O3-funroll循环编译之后,我在现代CPU上得到的每个元素不到1ns
我需要这个操作在没有线程的情况下工作快2-3倍。我该怎么做
另外,我还调查了AVX512位正是查找表的大小!指令集,但它缺少8位收集操作 with-march=native这是循环编译的目的:
movq %rax, %rbx
xorl %eax, %eax
.L145:
movzbl 128(%rsp,%rax), %edx
movzbl 64(%rsp,%rdx), %edx
movb %dl, 1000128(%rsp,%rax)
addq $1, %rax
cmpq $1000000, %rax
jne .L145
我很难想象,如果没有并行化,它是如何变得更快的
通过将类型更改为int32_t,它将得到矢量化:
vpcmpeqd %ymm2, %ymm2, %ymm2
movq %rax, %rbx
xorl %eax, %eax
.L145:
vmovdqa -8000048(%rbp,%rax), %ymm1
vmovdqa %ymm2, %ymm3
vpgatherdd %ymm3, -8000304(%rbp,%ymm1,4), %ymm0
vmovdqa %ymm0, -4000048(%rbp,%rax)
addq $32, %rax
cmpq $4000000, %rax
jne .L145
vzeroupper
这会有帮助吗?您的代码已经非常快了。然而 在我的系统上,更改时执行速度大约快4.858%
const int n_indices = 1000000;
到
这不算多,但很重要 索引和结果向量在内存中的不同位置,但可以同时访问。它会导致缓存未命中。我建议您将结果和索引合并到一个向量中。代码如下:
#include <iostream>
#include <stdint.h>
#include <random>
#include <chrono>
#include <ctime>
#define TYPE uint8_t
#define n_lookup 64
int main(){
const int n_indices = 2000000;
TYPE lookup[n_lookup];
// Merge indices and result
// If i is index, then i+1 is result
TYPE ind_res[n_indices];
// preparations
std::default_random_engine generator;
std::uniform_int_distribution<int> distribution(0, n_lookup);
for (int i=0; i < n_indices; i += 2) ind_res[i] = distribution(generator);
for (int i=0; i < n_lookup; i++) lookup[i] = distribution(generator);
std::chrono::time_point<std::chrono::system_clock> start = std::chrono::system_clock::now();
// main loop:
for (int i=0; i < n_indices; i += 2) {
ind_res[i+1] = lookup[ind_res[i]]; // more dense access here, no cache-miss
}
std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end - start;
std::cout << "computation took " << elapsed_seconds.count() * 1e9 / n_indices << " ns per element"<< std::endl;
// printing random numbers to avoid code elimination
std::cout << ind_res[24] << ind_res[90];
return 0;
}
我的测试表明,这段代码运行得更快。首先,有一个bug,distribution0,64生成数字0到64,64不能放入数组 通过一次查找两个值,可以加快查找2x的速度:
#include <iostream>
#include <stdint.h>
#include <random>
#include <chrono>
#include <ctime>
#define TYPE uint8_t
#define TYPE2 uint16_t
#define n_lookup 64
void tst() {
const int n_indices = 1000000;// has to be multiple of 2
TYPE lookup[n_lookup];
TYPE indices[n_indices];
TYPE result[n_indices];
TYPE2 lookup2[n_lookup * 256];
// preparations
std::default_random_engine generator;
std::uniform_int_distribution<int> distribution(0, n_lookup-1);
for (int i = 0; i < n_indices; i++) indices[i] = distribution(generator);
for (int i = 0; i < n_lookup; i++) lookup[i] = distribution(generator);
for (int i = 0; i < n_lookup; ++i) {
for (int j = 0; j < n_lookup; ++j) {
lookup2[(i << 8) | j] = (lookup[i] << 8) | lookup[j];
}
}
std::chrono::time_point<std::chrono::system_clock> start = std::chrono::system_clock::now();
TYPE2* indices2 = (TYPE2*)indices;
TYPE2* result2 = (TYPE2*)result;
// main loop:
for (int i = 0; i < n_indices / 2; ++i) {
*result2++ = lookup2[*indices2++];
}
std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::now();
for (int i = 0; i < n_indices; i++) {
if (result[i] != lookup[indices[i]]) {
std::cout << "!!!!!!!!!!!!!ERROR!!!!!!!!!!!!!";
}
}
std::chrono::duration<double> elapsed_seconds = end - start;
std::cout << "computation took " << elapsed_seconds.count() * 1e9 / n_indices << " ns per element" << std::endl;
// printing random numbers to avoid code elimination
std::cout << result[12] << result[45];
}
int main() {
tst();
std::cin.get();
return 0;
}
你能缓存结果吗?@Galik,不,不幸的是,这对这个问题没有任何意义。嗨,我已经用uint32_t和-march=native对代码进行了基准测试,速度降低了2倍。但是我的编译器没有使用矢量化。g++lookup.cpp-std=gnu++11-O3-funroll循环-march=native-mavx2,它是gcc 5.4。你知道为什么吗?好的,我知道了。我使用的是uint32\t,矢量化不起作用。现在我使用的是int32_t,代码是矢量化的,我看到了vpgatherdd,但应用程序不会将任何内容打印到output.OMG。这将每个元件的时间减少到大约0.25纳秒!我不明白为什么在使用两个数组而不是一个数组时会出现缓存未命中。你能解释一下吗?相当令人惊讶=当我学习cpu缓存时,我面对这种行为,我真的很困惑。我认为我与访问1L和2L缓存之间的时间差有关。由于1L缓存只能累积2或4行,因此很有可能从中提取数据。因此,当您使用两个阵列时,需要两行1L缓存,但当您使用一个阵列时,只需要一行。cache-miss的cance降低了2倍,更神秘。我试着用不同的移位加载/写入:result[I]=lookup[index[I+shift]]。在某些移位情况下,我希望一级缓存中不会出现冲突,但速度并不取决于移位。魔法?我用运算码得到0,44ns,用这个码得到0,58ns。您应该将时间乘以2,因为n_索引现在不是索引数。感谢您指出错误。很好的方法,在我的测试中显示了大约25-30%的加速。我的加速是2倍,显然系统是不同的。您可以尝试将主循环移动到单独的功能或执行手动循环展开,但这些只是瞎拍。
#include <iostream>
#include <stdint.h>
#include <random>
#include <chrono>
#include <ctime>
#define TYPE uint8_t
#define TYPE2 uint16_t
#define n_lookup 64
void tst() {
const int n_indices = 1000000;// has to be multiple of 2
TYPE lookup[n_lookup];
TYPE indices[n_indices];
TYPE result[n_indices];
TYPE2 lookup2[n_lookup * 256];
// preparations
std::default_random_engine generator;
std::uniform_int_distribution<int> distribution(0, n_lookup-1);
for (int i = 0; i < n_indices; i++) indices[i] = distribution(generator);
for (int i = 0; i < n_lookup; i++) lookup[i] = distribution(generator);
for (int i = 0; i < n_lookup; ++i) {
for (int j = 0; j < n_lookup; ++j) {
lookup2[(i << 8) | j] = (lookup[i] << 8) | lookup[j];
}
}
std::chrono::time_point<std::chrono::system_clock> start = std::chrono::system_clock::now();
TYPE2* indices2 = (TYPE2*)indices;
TYPE2* result2 = (TYPE2*)result;
// main loop:
for (int i = 0; i < n_indices / 2; ++i) {
*result2++ = lookup2[*indices2++];
}
std::chrono::time_point<std::chrono::system_clock> end = std::chrono::system_clock::now();
for (int i = 0; i < n_indices; i++) {
if (result[i] != lookup[indices[i]]) {
std::cout << "!!!!!!!!!!!!!ERROR!!!!!!!!!!!!!";
}
}
std::chrono::duration<double> elapsed_seconds = end - start;
std::cout << "computation took " << elapsed_seconds.count() * 1e9 / n_indices << " ns per element" << std::endl;
// printing random numbers to avoid code elimination
std::cout << result[12] << result[45];
}
int main() {
tst();
std::cin.get();
return 0;
}