假设我们有一系列这样的整数:
const int size = 100000;
int array[size];
//set some items to 0 and other items to 1
我想将所有值为1的项替换为其他值,例如123456。 这可以通过以下方式轻松实现:
for(int i = 0; i < size ; i++){
if(array[i] != 0)
array[i] = 123456;
}
出于好奇,有没有更快的方法来实现这一点,通过某种x86技巧,或者这是处理器的最佳代码?
答案 0 :(得分:46)
对于您最初拥有0和1的特定情况,以下可能更快。你必须对它进行基准测试。尽管如此,你可能无法用普通的C做得更好;如果你想利用可能存在的“x86技巧”,你可能需要深入装配。
for(int i = 0; i < size ; i++){
array[i] *= 123456;
}
基准代码:
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
size_t diff(struct timespec *start, struct timespec *end)
{
return (end->tv_sec - start->tv_sec)*1000000000 + end->tv_nsec - start->tv_nsec;
}
int main(void)
{
const size_t size = 1000000;
int array[size];
for(size_t i=0; i<size; ++i) {
array[i] = rand() & 1;
}
struct timespec start, stop;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &start);
for(size_t i=0; i<size; ++i) {
array[i] *= 123456;
//if(array[i]) array[i] = 123456;
}
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &stop);
printf("size: %zu\t nsec: %09zu\n", size, diff(&start, &stop));
}
计算机:四核AMD Phenom @ 2.5GHz,Linux,GCC 4.7,用
编译$ gcc arr.c -std=gnu99 -lrt -O3 -march=native
if
版本:~5-10ms *=
版本:~1.3ms 答案 1 :(得分:15)
对于像你这样的小数组试图找到另一种算法是没有用的,如果这些值不是特定的模式,那么简单的循环是唯一的方法。
但是,如果你有一个非常大的数组(我们正在谈论几百万个条目),那么你可以将工作分成线程。每个单独的线程处理整个数据集的较小部分。
答案 2 :(得分:13)
您可能也希望对此进行基准测试:
for(int i = 0; i < size ; i++){
array[i] = (~(array[i]-1) & 123456);
}
我通过与SchighSchagh相同的基准测试,我的设置几乎没有差别。但是,它可能会有所不同。
编辑:停止按下!
我只记得如果“:”之间的参数是常量,x86可以“取消分支”三元运算符。请考虑以下代码:
for(size_t i=0; i<size; ++i) {
array[i] = array[i] ? 123456 : 0;
}
看起来几乎像你的原始代码,不是吗?好吧,反汇编显示它已经编译而没有任何分支:
for(size_t i=0; i<size; ++i) {
00E3104C xor eax,eax
00E3104E mov edi,edi
array[i] = array[i] ? 123456 : 0;
00E31050 mov edx,dword ptr [esi+eax*4]
00E31053 neg edx
00E31055 sbb edx,edx
00E31057 and edx,1E240h
00E3105D mov dword ptr [esi+eax*4],edx
00E31060 inc eax
00E31061 cmp eax,5F5E100h
00E31066 jb wmain+50h (0E31050h)
}
在性能方面,它似乎与我的原始和SchighSchagh解决方案相提并论。但它更具可读性和灵活性。例如,它可以使用值为0和1的数组[i]。
底线,基准和看一眼反汇编。
答案 3 :(得分:7)
数组足够小,适合缓存,所以使用SIMD值得:(未经测试)
mov ecx, size
lea esi, [array + ecx * 4]
neg ecx
pxor xmm0, xmm0
movdqa xmm1, [_vec4_123456] ; value of { 123456, 123456, 123456, 123456 }
_replaceloop:
movdqa xmm2, [esi + ecx * 4] ; assumes the array is 16 aligned, make that true
add ecx, 4
pcmpeqd xmm2, xmm0
pandn xmm2, xmm1
movdqa [esi + ecx * 4 - 16], xmm2
jnz _replaceloop
按2展开可能会有所帮助。
如果你有SSE4.1,你可以使用SchighSchagh的乘法技巧pmulld
。
答案 4 :(得分:3)
这里有一些Win32代码来分析各种版本的算法(使用VS2010 Express使用默认版本构建编译): -
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
const size_t
size = 0x1D4C00;
_declspec(align(16)) int
g_array [size];
_declspec(align(16)) int
_vec4_123456 [] = { 123456, 123456, 123456, 123456 };
void Test (void (*fn) (size_t, int *), char *test)
{
printf ("Executing test: %s\t", test);
for(size_t i=0; i<size; ++i) {
g_array[i] = rand() & 1;
}
LARGE_INTEGER
start,
end;
QueryPerformanceCounter (&start);
fn (size, g_array);
QueryPerformanceCounter (&end);
printf("size: %u\t count: %09u\n", size, (int) (end.QuadPart - start.QuadPart));
}
void Test1 (size_t size, int *array)
{
for(size_t i=0; i<size; ++i) {
array[i] *= 123456;
}
}
void Test2 (size_t size, int *array)
{
for(size_t i=0; i<size; ++i) {
if(array[i]) array[i] = 123456;
}
}
void Test3 (size_t array_size, int *array)
{
__asm
{
mov edi,array
mov ecx, array_size
lea esi, [edi + ecx * 4]
neg ecx
pxor xmm0, xmm0
movdqa xmm1, [_vec4_123456] ; value of { 123456, 123456, 123456, 123456 }
_replaceloop:
movdqa xmm2, [esi + ecx * 4] ; assumes the array is 16 aligned, make that true
add ecx, 4
pcmpeqd xmm2, xmm0
pandn xmm2, xmm1
movdqa [esi + ecx * 4 - 16], xmm2
jnz _replaceloop
}
}
void Test4 (size_t array_size, int *array)
{
array_size = array_size * 8 / 12;
__asm
{
mov edi,array
mov ecx,array_size
lea esi,[edi+ecx*4]
lea edi,[edi+ecx*4]
neg ecx
mov edx,[_vec4_123456]
pxor xmm0,xmm0
movdqa xmm1,[_vec4_123456]
replaceloop:
movdqa xmm2,[esi+ecx*4]
mov eax,[edi]
mov ebx,[edi+4]
movdqa xmm3,[esi+ecx*4+16]
add edi,16
add ecx,9
imul eax,edx
pcmpeqd xmm2,xmm0
imul ebx,edx
pcmpeqd xmm3,xmm0
mov [edi-16],eax
mov [edi-12],ebx
pandn xmm2,xmm1
mov eax,[edi-8]
mov ebx,[edi-4]
pandn xmm3,xmm1
imul eax,edx
movdqa [esi+ecx*4-36],xmm2
imul ebx,edx
movdqa [esi+ecx*4-20],xmm3
mov [edi-8],eax
mov [edi-4],ebx
loop replaceloop
}
}
int main()
{
Test (Test1, "Test1 - mul");
Test (Test2, "Test2 - branch");
Test (Test3, "Test3 - simd");
Test (Test4, "Test4 - simdv2");
}
它有测试:C使用if()...
,C使用乘法,harold的simd版本和我的simd版本。
多次运行(请记住,在进行性能分析时,您应该对多次运行的结果进行平均分析)所有版本之间的差异很小,除了分支的版本要慢得多。
这并不奇怪,因为algortihm对每个记忆项目的工作量很小。这意味着真正的限制因素是CPU和内存之间的带宽,CPU一直在等待内存赶上,即使cpu帮助预取数据(ia32的检测和线性预取数据)。 / p>
答案 5 :(得分:2)
这可能会更快。
for(int i = 0; i < size ; i++){
array[i] = ((123456 << array[i]) - 123456);
}
编辑:将按位操作更改为左移。
答案 6 :(得分:2)
您可以使用另一个数组或其他数据结构来跟踪您设置为1的元素的索引,然后只访问这些元素。如果只有少数元素设置为一个
,这将最有效答案 7 :(得分:0)
另一种加速数组赋值的方法是使用c内联汇编。如下所示,
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
const int size = 100000;
void main(void) {
int array[size];
int value = 1000;
__asm__ __volatile__("cld\n\t"
"rep\n\t"
"stosl\n\t"
:
:"c"(size*4), "a"(value), "D"(array)
:
);
printf("Array[0] : %d \n", array[0]);
}
当我们与普通c程序进行比较以分配数组值时,这应该是速度。此外, stosl 指令需要4个时钟周期。