无心人
发表于 2010-12-22 09:28:02
读会同时读到cache,写直接写内存的
难道是读比写慢的原因
mathe
发表于 2010-12-22 11:58:35
写也是到Cache,不一定到内存,所以像下面的代码:
初时x=0,y=0
线程1:
x=1;
y=1;
...
线程2:
if(y==1&&x==0){
...
}
线程2这个if中的内容是有可能被执行到的.
G-Spider
发表于 2010-12-27 18:21:29
8# liangbch
奇怪,你说带宽为6.4GB/s,你的read为6826MB/s >6.4GB/s ,难道还没有真正达到内存瓶颈?
liangbch
发表于 2010-12-28 13:13:55
我认为, 测试软件得到的结果并不是100%精确,他总是存在误差,所有9#的测试结果大于理论值。
我家那台老电脑是DDR400,但是实测内存带宽明显小于理论值。
11#的内存也是DDR400, 理论带宽是3.2GB/s,但11#给出的内存读性能却比理论值高出好多,竟达到4.1GB/s
G-Spider
发表于 2010-12-28 13:56:11
24# liangbch
这样想就好了,呵呵,那个getMaxMin_SSE4已在我这机子没啥潜力了,直接抱走。。:lol
liangbch
发表于 2011-1-1 23:16:48
1# liangbch
采用32M的测试样本,运行在Intel I5-750 4核CPU,同时测试这6个函数。每个函数均取5次测试结果的最小值,结果如下:
getMaxMin1: 0.064071 seconds
getMaxMin_asm:0.065478 seconds
getMaxMin_SSE4: 0.016766 seconds
getMaxMin_MT2_v1: 0.027156 seconds
getMaxMin_MT4_v1: 0.014782 seconds
getMaxMin_MT2_v2: 0.014681 seconds
getMaxMin_MT4_v4: 0.014684 seconds
liangbch
发表于 2011-1-2 21:53:24
到目前为止,我编写了12个版本的求一个无符号整数数组的最大值和最小值的函数,函数名称和运行速度见下。
测试环境:i5-750四核CPU, DDR3-1333,2G内存, 测试的数据为32M个无符号整数,给出的时间值为5次测试的最小值。
getMaxMin1: 0.060888 秒
getMaxMin1_asm: 0.065483 秒
getMaxMin1_SSE4: 0.016788 秒
getMaxMin2: 0.128427 秒
getMaxMin2_asm1: 0.040059 秒
getMaxMin2_asm2: 0.040233 秒
getMaxMin_MT2_v1: 0.027078 秒
getMaxMin_MT4_v1: 0.014741 秒
getMaxMin_MT2_v2: 0.014625 秒
getMaxMin_MT4_v2: 0.014714 秒
getMaxMin_MT2_v3: 0.020532 秒
getMaxMin_MT4_v3: 0.014684 秒
函数getMaxMin1是一个最普通的版本,使用C语言书写,每个数需作2次比较,使用随机数据,CPU的分支预测部件预测成功的概率非常高。故速度很快。
函数getMaxMin1_asm是一个使用汇编语言书写的版本,使用 cmovb 指令消除分支,使得程序的性能与分支预测无关,速度得到保证,相比能够准确做分支预测的版本getMaxMin1,速度稍慢。测试结果表明,如果分支预测的正确率很高,消除分支并不能提高速度。
函数getMaxMin1_SSE4是一个使用汇编语言书写的版本,不同getMaxMin1_asm,这个函数使用SSE4 指令PMINUD和PMAXUD,一次可求4个DWORD的最大或者最小值,内存读则使用movdqa指令,一次读取4个DWORD,这个版本充分展示了SSE4指令的威力,速度竟达 getMaxMin1_asm 的四倍,几乎吃掉了整个内存带宽。
函数 getMaxMin2: 也是用C语言的书写的版本,每2数需要3次比较,比较次数较getMaxMin1更少,但是每2个数的3次比较中,第一次比较时是一个数据依赖的分支,CPU分支预测的准确率大约为50%,分支预测失败的惩罚大大降低了速度,速度仅为getMaxMin1的50%左右。
函数getMaxMin2_asm1:这个函数是 getMaxMin2的汇编版本,使用cmovb 指令消除了每2个数的3次比较,消除分子的效果非常明显,速度竟提高到300%。
函数getMaxMin2_asm2:这个函数也是 getMaxMin2的汇编版本,在每个循环步中,比getMaxMin2_asm1多了1条指令,但减少了内存访问,内存访问的减少并没有完全抵消一条mov指令的开销,速度和getMaxMin2_asm1大体相当,但多次测试的结果显示比getMaxMin2_asm1略慢。
后6个函数是2线程和4线程的版本。
getMaxMin_MT2_v1是getMaxMin1的2线程版本,速度提高到单线程版本的200%以上,在误差范围内,速度为单线程版本的2倍,和理论吻合。
getMaxMin_MT4_v1是getMaxMin1的4线程版本,速度提高到单线程版本的400%以上,在误差范围内,速度为单线程版本的4倍左右,和理论吻合。
getMaxMin_MT2_v2是getMaxMin1_SSE4的2线程版本,速度仅仅提高13%,一个线程的getMaxMin1_SSE4函数几乎吃到了整个内存带宽,故getMaxMin_MT2_v2性能的提高受限于内存带宽,并没有达到单线程版本的200%。
getMaxMin_MT4_v2是getMaxMin1_SSE4的4线程版本,速度和双线程版本getMaxMin_MT2_v2相同。性能的提高受限于内存带宽,并没有达到单线程版本的400%。
getMaxMin_MT2_v3是getMaxMin2_asm1的2线程版本,速度为单线程版本的195%,在误差范围内,速度为单线程版本的2倍,和理论吻合。
getMaxMin_MT4_v3是getMaxMin2_asm1的4线程版本,性能的提高受限于内存带宽,并没有达到单线程版本的400%。
以下4个多线程版本函数的性能几乎完全相同,而且实际性能都小于理论值,说明 多线程函数的执行单元虽然是独立的,但他们共享相同的物理内存,多线程并行的性能受限于内存的总带宽,当总的内存存取达到物理内存带宽的限制,增加线程数并不能提高实际性能。
getMaxMin_MT4_v1: 0.014741 秒
getMaxMin_MT2_v2: 0.014625 秒
getMaxMin_MT4_v2: 0.014714 秒
getMaxMin_MT4_v3: 0.014684 秒
liangbch
发表于 2011-1-2 22:06:50
给出6个单线程函数的源代码#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include "defs.h"
#include "findMaxMin.h"
void getMaxMin1(DWORD *pData,int len,DWORD *min,DWORD *max)
{
int i;
*min=~0;
*max=0;
for (i=0; i<len; i++)
{
if ( pData[ i ] > *max)
*max=pData[ i ];
if (pData[ i ]< *min)
*min=pData[ i ];
}
}
void getMaxMin1_asm(DWORD *pData,int len,DWORD *min,DWORD *max)
{
DWORD _min, _max;
__asm
{
mov esi, pData
mov ecx, len
lea edi,
mov eax, 0xffffffff //min
mov edx, 0 //max
loop_start:
cmp esi, edi
jge next10
cmp , eax
cmovb eax,
cmp , edx
cmova edx,
add esi, 4
jmp loop_start
next10:
mov_min, eax
mov_max, edx
}
*min=_min;
*max=_max;
}
void getMaxMin2(DWORD *pData,int len,DWORD *min,DWORD *max)
{
int i;
if (len % 2 ==0 )
{
if ( pData > pData )
{
*max= pData;
*min= pData;
}
else
{
*max= pData;
*min= pData;
}
i=2;
}
else
{
*max= *min= pData;
i=1;
}
for (;i<len;i+=2)
{
if ( pData[ i ] > pData[ i+1] )
{
if ( pData[ i ]> *max)
*max=pData[ i ];
if ( pData[ i+1]< *min)
*min=pData[ i+1];
}
else
{
if ( pData[ i ] < *min)
*min=pData[ i ];
if ( pData[ i+1]> *max)
*max=pData[ i+1];
}
}
}
void getMaxMin2_asm1(DWORD *pData,int len,DWORD *min,DWORD *max)
{
DWORD _min, _max;
__asm
{
mov esi, pData
mov ecx, len
lea edi,
and ecx,1
jz next10
//next00:next % 2 ==1
mov eax,
mov edx,
add esi,4
jmp next20
next10:// len % 2 ==0
mov eax, // eax: min
mov edx, // edx, max
cmp eax,edx
jb next15
xchgeax,edx
next15:
add esi,8
jmp next20
loop_start:
mov ebx, //ebx=min(pData , pData [ i+1]
mov ecx, //ebx=max(pData , pData [ i+1]
cmp ebx,ecx
cmova ebx,
cmova ecx,
cmp eax, ebx
cmova eax, ebx
cmp edx, ecx
cmovb edx, ecx
add esi, 8
next20:
cmp esi, edi
jb loop_start
mov _min, eax
mov _max, edx
}
*min=_min;
*max=_max;
}
_declspec(naked)
void getMaxMin2_asm2(DWORD *pData,int len,DWORD *min,DWORD *max)
{
#define PAR_PDATA4
#define PAR_LEN 8
#define PAR_MIN 12
#define PAR_MAX 16
#define REG_MINesi
#define REG_MAXedi
#define REG_CUR_TMPecx
#define REG_CUR_MINeax
#define REG_CUR_MAXedx
#define REG_PDATA ebx // pData
#define REG_PEND ebp // pData+len
__asm
{
push ebx
push ebp
push esi
push edi
mov REG_PDATA, dword ptr
mov ecx,dword ptr
test ecx,1
jz next10
//next00:next % 2 ==1
lea REG_PEND,
mov REG_MIN,
mov REG_MAX,
add REG_PDATA,4
jmp next20
next10:// len % 2 ==0
lea REG_PEND,
mov REG_MIN,
mov REG_MAX,
cmp REG_MIN,REG_MAX
jb next15
xchgREG_MIN,REG_MAX
next15:
add REG_PDATA,8
jmp next20
loop_start:
mov REG_CUR_MIN, //ebx=min(pData , pData [ i+1]
mov REG_CUR_MAX, //ebx=max(pData , pData [ i+1]
cmp REG_CUR_MIN, REG_CUR_MAX
mov REG_CUR_TMP, REG_CUR_MIN
cmova REG_CUR_MIN, REG_CUR_MAX
cmova REG_CUR_MAX, REG_CUR_TMP
cmp REG_MIN, REG_CUR_MIN
cmova REG_MIN, REG_CUR_MIN
cmp REG_MAX, REG_CUR_MAX
cmovb REG_MAX, REG_CUR_MAX
add REG_PDATA, 8
next20:
cmp REG_PDATA, REG_PEND
jb loop_start
mov eax,
mov edx,
mov , REG_MIN
mov , REG_MAX
pop edi
pop esi
pop ebp
pop ebx
ret
}
}
_declspec(naked)
void getMaxMin1_SSE4(DWORD *pData,int len,DWORD *min,DWORD *max)
// 使用SSE4.1 指令 计算数组的最大最小值
// 寄存器的使用
// eax: 存放最小值
// edx: 存放最大值
// xmm0: 当前取得的4个整数
// xmm1: 存放最小值
// xmm2: 存放最大值
/* PMINUD and PMAXUD are SSE4.1 instruciton
The usage for PMINUD
Compares packed unsigned dword integers in the destination operand (first operand)
and the source operand (second operand), and returns the minimum for each packed
value in the destination operand.
Operation
IF (DEST < SRC)
THEN DEST 􀃅 DEST;
ELSE DEST 􀃅 SRC; FI;
IF (DEST < SRC)
THEN DEST 􀃅 DEST;
ELSE DEST 􀃅 SRC; FI;
IF (DEST < SRC)
THEN DEST 􀃅 DEST;
ELSE DEST 􀃅 SRC; FI;
IF (DEST < SRC)
THEN DEST 􀃅 DEST;
ELSE DEST 􀃅 SRC; FI;
*/
#define MIN_128REG xmm1
#define MAX_128REG xmm2
#define MIN_32REG eax
#define MAX_32REG edx
#define PT_REG esi
#define END_REG edi
#define PAR_PDATA 4
#define PAR_LEN 8
#define PAR_MIN 12
#define PAR_MAX 16
{
__asm
{
push esi
push edi
mov MIN_32REG,0xffffffff // min=0xffffffff
xor MAX_32REG,MAX_32REG // max=0
//phase1:
mov ecx, dword ptr // len
mov PT_REG, dword ptr// pData
lea END_REG, // edi=pData + len
jmp cmp10
loop1_start:
cmp , MIN_32REG
cmovbMIN_32REG,
cmp , MAX_32REG
cmovaMAX_32REG,
add PT_REG, 4
cmp10:
test PT_REG, 0x0f
jz phase2
cmp PT_REG, END_REG
jb loop1_start
phase2:
mov ecx, dword ptr // len
mov END_REG, dword ptr// pData
lea END_REG, // edi=pData + len
and END_REG, 0xfffffff0 // clear bit0-bit3 and make edi % 16==0
movd MIN_128REG, MIN_32REG
pshufdMIN_128REG, MIN_128REG, 00000000b // xmm1 = R0:R0:R0:R0
movd MAX_128REG, MAX_32REG
pshufdMAX_128REG, MAX_128REG, 00000000b // xmm2 = R0:R0:R0:R0
jmp cmp20
loop2_start:
movdqaxmm0, xmmword ptr
PMINUDMIN_128REG, xmm0
PMAXUDMAX_128REG, xmm0
add PT_REG,16
cmp20:
cmp PT_REG, END_REG
jb loop2_start
phase3:
movd ecx, MIN_128REG
cmp ecx, MIN_32REG
cmovb MIN_32REG,ecx //MIN_32REG=min(min_32REG,bit0-31(MIN_128REG))
psrldqMIN_128REG, 4 //MIN_128REG >>=4
movd ecx, MIN_128REG
cmp ecx, MIN_32REG
cmovb MIN_32REG,ecx //MIN_32REG=min(min_32REG,bit32-63(MIN_128REG))
psrldqMIN_128REG, 4 //MIN_128REG >>=4
movd ecx, MIN_128REG
cmp ecx, MIN_32REG
cmovb MIN_32REG,ecx //MIN_32REG=min(min_32REG,bit64-95(MIN_128REG))
psrldqMIN_128REG, 4 //MIN_128REG >>=4
movd ecx, MIN_128REG
cmp ecx, MIN_32REG
cmovb MIN_32REG,ecx //MIN_32REG=min(min_32REG,bit96-127(MIN_128REG) )
//-----------------------------------------------
movd ecx, MAX_128REG
cmp ecx, MAX_32REG
cmova MAX_32REG,ecx //MAX_32REG=max(max_32REG,bit0-31(MAX_128REG))
psrldqMAX_128REG, 4 //MAX_128REG >>=4
movd ecx, MAX_128REG
cmp ecx, MAX_32REG
cmova MAX_32REG,ecx //MAX_32REG=max(max_32REG,bit32-63(MAX_128REG))
psrldqMAX_128REG, 4 //MAX_128REG >>=4
movd ecx, MAX_128REG
cmp ecx, MAX_32REG
cmova MAX_32REG,ecx //MAX_32REG=max(max_32REG,bit64-95(MAX_128REG))
psrldqMAX_128REG, 4 //MAX_128REG >>=4
movd ecx, MAX_128REG
cmp ecx, MAX_32REG
cmova MAX_32REG,ecx //MIN_32REG=max(max_32REG,bit96-127(MIN_128REG))
mov END_REG, dword ptr// pData
mov ecx, dword ptr // len
lea END_REG, // edi=pData + len
jmp cmp30
loop3_start:
cmp , MIN_32REG
cmovbMIN_32REG,
cmp , MAX_32REG
cmovaMAX_32REG,
add PT_REG, 4
cmp30:
cmp PT_REG, END_REG
jb loop3_start
RET_VALUE:
mov ecx, dword ptr //*min
mov dword ptr ,MIN_32REG
mov ecx, dword ptr //*max
mov dword ptr ,MAX_32REG
emms
pop edi
pop esi
ret
}
}
liangbch
发表于 2011-1-2 22:14:00
本楼给出 包括VC2008工程文件的所有源代码。
gxqcn
发表于 2011-1-4 08:19:38
非常赞赏这种讨论方式,有代码、有数据、有分析结论,相当完整,非常方便大家参考。