Mar*_*rco 11 c# double performance integer
我正在编写一个C#类来使用整数执行2D可分卷积,以获得比双对应更好的性能.问题是我没有获得真正的性能提升.
这是X过滤器代码(对于int和double两种情况都有效):
foreach (pixel)
{
int value = 0;
for (int k = 0; k < filterOffsetsX.Length; k++)
{
value += InputImage[index + filterOffsetsX[k]] * filterValuesX[k]; //index is relative to current pixel position
}
tempImage[index] = value;
}
Run Code Online (Sandbox Code Playgroud)
在整数情况"值"中,"InputImage"和"tempImage"是"int","Image <byte>"和"Image <int>"类型.
在双重"值"中,"InputImage"和"tempImage"是"double","Image <double>"和"Image <double>"类型.
(filterValues在每种情况下都是int [])
(类Image <T>是extern dll的一部分.它应该类似于.NET Drawing Image类..).
我的目标是通过int + =(byte*int)vs double + =(double*int)实现快速性能
以下时间是200次重复的平均值.
滤波器大小9 = 0.031(双精度)0.027(int)
滤波器大小13 = 0.042(双精度)0.038(int)
滤波器大小25 = 0.078(双精度)0.070(int)
性能提升很小.这可能是由管道停滞和次优代码引起的吗?
编辑:简化删除不重要的变量的代码.
编辑2:我认为我没有与缓存未命中相关的问题,因为"索引"遍历相邻的存储单元(逐行方式).此外,"filterOffstetsX"仅包含相对于同一行上的像素的小偏移,并且在滤波器大小/ 2的最大距离处.该问题可以存在于第二可分离滤波器(Y滤波器)中,但时间不是那么不同.
Ben*_*igt 16
使用Visual C++,因为我可以确定我是计时算术运算而不是其他.
结果(每次操作执行6亿次):
i16 add: 834575
i32 add: 840381
i64 add: 1691091
f32 add: 987181
f64 add: 979725
i16 mult: 850516
i32 mult: 858988
i64 mult: 6526342
f32 mult: 1085199
f64 mult: 1072950
i16 divide: 3505916
i32 divide: 3123804
i64 divide: 10714697
f32 divide: 8309924
f64 divide: 8266111
freq = 1562587
Run Code Online (Sandbox Code Playgroud)
CPU是Intel Core i7,Turbo Boosted到2.53 GHz.
基准代码:
#include <stdio.h>
#include <windows.h>
template<void (*unit)(void)>
void profile( const char* label )
{
static __int64 cumtime;
LARGE_INTEGER before, after;
::QueryPerformanceCounter(&before);
(*unit)();
::QueryPerformanceCounter(&after);
after.QuadPart -= before.QuadPart;
printf("%s: %I64i\n", label, cumtime += after.QuadPart);
}
const unsigned repcount = 10000000;
template<typename T>
void add(volatile T& var, T val) { var += val; }
template<typename T>
void mult(volatile T& var, T val) { var *= val; }
template<typename T>
void divide(volatile T& var, T val) { var /= val; }
template<typename T, void (*fn)(volatile T& var, T val)>
void integer_op( void )
{
unsigned reps = repcount;
do {
volatile T var = 2000;
fn(var,5);
fn(var,6);
fn(var,7);
fn(var,8);
fn(var,9);
fn(var,10);
} while (--reps);
}
template<typename T, void (*fn)(volatile T& var, T val)>
void fp_op( void )
{
unsigned reps = repcount;
do {
volatile T var = (T)2.0;
fn(var,(T)1.01);
fn(var,(T)1.02);
fn(var,(T)1.03);
fn(var,(T)2.01);
fn(var,(T)2.02);
fn(var,(T)2.03);
} while (--reps);
}
int main( void )
{
LARGE_INTEGER freq;
unsigned reps = 10;
do {
profile<&integer_op<__int16,add<__int16>>>("i16 add");
profile<&integer_op<__int32,add<__int32>>>("i32 add");
profile<&integer_op<__int64,add<__int64>>>("i64 add");
profile<&fp_op<float,add<float>>>("f32 add");
profile<&fp_op<double,add<double>>>("f64 add");
profile<&integer_op<__int16,mult<__int16>>>("i16 mult");
profile<&integer_op<__int32,mult<__int32>>>("i32 mult");
profile<&integer_op<__int64,mult<__int64>>>("i64 mult");
profile<&fp_op<float,mult<float>>>("f32 mult");
profile<&fp_op<double,mult<double>>>("f64 mult");
profile<&integer_op<__int16,divide<__int16>>>("i16 divide");
profile<&integer_op<__int32,divide<__int32>>>("i32 divide");
profile<&integer_op<__int64,divide<__int64>>>("i64 divide");
profile<&fp_op<float,divide<float>>>("f32 divide");
profile<&fp_op<double,divide<double>>>("f64 divide");
::QueryPerformanceFrequency(&freq);
putchar('\n');
} while (--reps);
printf("freq = %I64i\n", freq);
}
Run Code Online (Sandbox Code Playgroud)
我使用Visual C++ 2010 32位进行了默认优化构建.
每次调用profile,add,mult,和divide(环内),得到了内联.函数调用仍然生成profile,但由于每次调用完成了6000万次操作,我认为函数调用开销并不重要.
即使volatile抛出,Visual C++优化编译器也是SMART.我最初使用小整数作为右手操作数,编译器愉快地使用lea和add指令进行整数乘法运算.与普通的智慧所暗示的相比,你可以通过调用高度优化的C++代码获得更大的提升,因为C++优化器比任何JIT都做得好得多.
最初我var在循环外部进行了初始化,这使得浮点乘法代码因为不断溢出而运行缓慢.FPU处理NaN很慢,在编写高性能数字运算例程时要记住其他事项.
依赖关系也以防止流水线操作的方式设置.如果你想看到流水线的效果,请在评论中这样说,我将修改测试平台以对多个变量进行操作,而不仅仅是一个变量.
i32的反汇编乘法:
; COMDAT ??$integer_op@H$1??$mult@H@@YAXACHH@Z@@YAXXZ
_TEXT SEGMENT
_var$66971 = -4 ; size = 4
??$integer_op@H$1??$mult@H@@YAXACHH@Z@@YAXXZ PROC ; integer_op<int,&mult<int> >, COMDAT
; 29 : {
00000 55 push ebp
00001 8b ec mov ebp, esp
00003 51 push ecx
; 30 : unsigned reps = repcount;
00004 b8 80 96 98 00 mov eax, 10000000 ; 00989680H
00009 b9 d0 07 00 00 mov ecx, 2000 ; 000007d0H
0000e 8b ff npad 2
$LL3@integer_op@5:
; 31 : do {
; 32 : volatile T var = 2000;
00010 89 4d fc mov DWORD PTR _var$66971[ebp], ecx
; 33 : fn(var,751);
00013 8b 55 fc mov edx, DWORD PTR _var$66971[ebp]
00016 69 d2 ef 02 00
00 imul edx, 751 ; 000002efH
0001c 89 55 fc mov DWORD PTR _var$66971[ebp], edx
; 34 : fn(var,6923);
0001f 8b 55 fc mov edx, DWORD PTR _var$66971[ebp]
00022 69 d2 0b 1b 00
00 imul edx, 6923 ; 00001b0bH
00028 89 55 fc mov DWORD PTR _var$66971[ebp], edx
; 35 : fn(var,7124);
0002b 8b 55 fc mov edx, DWORD PTR _var$66971[ebp]
0002e 69 d2 d4 1b 00
00 imul edx, 7124 ; 00001bd4H
00034 89 55 fc mov DWORD PTR _var$66971[ebp], edx
; 36 : fn(var,81);
00037 8b 55 fc mov edx, DWORD PTR _var$66971[ebp]
0003a 6b d2 51 imul edx, 81 ; 00000051H
0003d 89 55 fc mov DWORD PTR _var$66971[ebp], edx
; 37 : fn(var,9143);
00040 8b 55 fc mov edx, DWORD PTR _var$66971[ebp]
00043 69 d2 b7 23 00
00 imul edx, 9143 ; 000023b7H
00049 89 55 fc mov DWORD PTR _var$66971[ebp], edx
; 38 : fn(var,101244215);
0004c 8b 55 fc mov edx, DWORD PTR _var$66971[ebp]
0004f 69 d2 37 dd 08
06 imul edx, 101244215 ; 0608dd37H
; 39 : } while (--reps);
00055 48 dec eax
00056 89 55 fc mov DWORD PTR _var$66971[ebp], edx
00059 75 b5 jne SHORT $LL3@integer_op@5
; 40 : }
0005b 8b e5 mov esp, ebp
0005d 5d pop ebp
0005e c3 ret 0
??$integer_op@H$1??$mult@H@@YAXACHH@Z@@YAXXZ ENDP ; integer_op<int,&mult<int> >
; Function compile flags: /Ogtp
_TEXT ENDS
Run Code Online (Sandbox Code Playgroud)
和f64相乘:
; COMDAT ??$fp_op@N$1??$mult@N@@YAXACNN@Z@@YAXXZ
_TEXT SEGMENT
_var$67014 = -8 ; size = 8
??$fp_op@N$1??$mult@N@@YAXACNN@Z@@YAXXZ PROC ; fp_op<double,&mult<double> >, COMDAT
; 44 : {
00000 55 push ebp
00001 8b ec mov ebp, esp
00003 83 e4 f8 and esp, -8 ; fffffff8H
; 45 : unsigned reps = repcount;
00006 dd 05 00 00 00
00 fld QWORD PTR __real@4000000000000000
0000c 83 ec 08 sub esp, 8
0000f dd 05 00 00 00
00 fld QWORD PTR __real@3ff028f5c28f5c29
00015 b8 80 96 98 00 mov eax, 10000000 ; 00989680H
0001a dd 05 00 00 00
00 fld QWORD PTR __real@3ff051eb851eb852
00020 dd 05 00 00 00
00 fld QWORD PTR __real@3ff07ae147ae147b
00026 dd 05 00 00 00
00 fld QWORD PTR __real@4000147ae147ae14
0002c dd 05 00 00 00
00 fld QWORD PTR __real@400028f5c28f5c29
00032 dd 05 00 00 00
00 fld QWORD PTR __real@40003d70a3d70a3d
00038 eb 02 jmp SHORT $LN3@fp_op@3
$LN22@fp_op@3:
; 46 : do {
; 47 : volatile T var = (T)2.0;
; 48 : fn(var,(T)1.01);
; 49 : fn(var,(T)1.02);
; 50 : fn(var,(T)1.03);
; 51 : fn(var,(T)2.01);
; 52 : fn(var,(T)2.02);
; 53 : fn(var,(T)2.03);
; 54 : } while (--reps);
0003a d9 ce fxch ST(6)
$LN3@fp_op@3:
0003c 48 dec eax
0003d d9 ce fxch ST(6)
0003f dd 14 24 fst QWORD PTR _var$67014[esp+8]
00042 dd 04 24 fld QWORD PTR _var$67014[esp+8]
00045 d8 ce fmul ST(0), ST(6)
00047 dd 1c 24 fstp QWORD PTR _var$67014[esp+8]
0004a dd 04 24 fld QWORD PTR _var$67014[esp+8]
0004d d8 cd fmul ST(0), ST(5)
0004f dd 1c 24 fstp QWORD PTR _var$67014[esp+8]
00052 dd 04 24 fld QWORD PTR _var$67014[esp+8]
00055 d8 cc fmul ST(0), ST(4)
00057 dd 1c 24 fstp QWORD PTR _var$67014[esp+8]
0005a dd 04 24 fld QWORD PTR _var$67014[esp+8]
0005d d8 cb fmul ST(0), ST(3)
0005f dd 1c 24 fstp QWORD PTR _var$67014[esp+8]
00062 dd 04 24 fld QWORD PTR _var$67014[esp+8]
00065 d8 ca fmul ST(0), ST(2)
00067 dd 1c 24 fstp QWORD PTR _var$67014[esp+8]
0006a dd 04 24 fld QWORD PTR _var$67014[esp+8]
0006d d8 cf fmul ST(0), ST(7)
0006f dd 1c 24 fstp QWORD PTR _var$67014[esp+8]
00072 75 c6 jne SHORT $LN22@fp_op@3
00074 dd d8 fstp ST(0)
00076 dd dc fstp ST(4)
00078 dd da fstp ST(2)
0007a dd d8 fstp ST(0)
0007c dd d8 fstp ST(0)
0007e dd d8 fstp ST(0)
00080 dd d8 fstp ST(0)
; 55 : }
00082 8b e5 mov esp, ebp
00084 5d pop ebp
00085 c3 ret 0
??$fp_op@N$1??$mult@N@@YAXACNN@Z@@YAXXZ ENDP ; fp_op<double,&mult<double> >
; Function compile flags: /Ogtp
_TEXT ENDS
Run Code Online (Sandbox Code Playgroud)
T.E*_*.D. 15
看起来你说你只是在你最长的情况下运行那个内循环5000次.最后我检查的FPU(不久前很久以前)只花了大约5个周期来执行乘法而不是整数单位.因此,通过使用整数,您可以节省大约25,000个CPU周期.假设没有缓存未命中或任何其他会导致CPU在任一事件中等待的情况.
假设现代英特尔酷睿CPU的时钟频率接近2.5Ghz,那么通过使用整数单元,您可以节省大约10 微秒的运行时间.有点微不足道.我以实时编程为生,即使我们错过了某个地方的截止日期,我们也不会在这里消耗那么多的CPU浪费.
尽管如此,digEmAll在评论中提出了一个非常好的观点.如果编译器和优化器正在完成他们的工作,整个过程就是流水线操作.这意味着实际上整个内部循环使用FPU运行的时间比整数单元长5个周期,而不是每个操作.如果是这种情况,您的预期时间节省将是如此之小,以至于难以衡量它们.
如果你真的做了足够的浮点操作来让整个shebang需要很长时间,我建议你做一下或多做以下事情: