배경:
어셈블리 언어가 내장 된 일부 파스칼 코드를 최적화하는 동안 불필요한 MOV
명령을 발견 하고 제거했습니다.
놀랍게도 불필요한 명령을 제거하면 프로그램 속도 가 느려졌습니다 .
임의의 쓸모없는 MOV
명령어 를 추가 하면 성능 이 더욱 향상됩니다 .
효과는 불규칙하며 실행 순서에 따라 변경 됩니다. 한 줄로 위나 아래로 같은 정크 명령이 바뀌면 속도가 느려 집니다.
나는 CPU가 모든 종류의 최적화와 능률화를 수행한다는 것을 이해하지만 이것은 흑 마법처럼 보입니다.
자료:
내 코드 버전은 시간 이 걸리는 루프 중간에 세 가지 정크 작업 을 조건부로 컴파일합니다 2**20==1048576
. 주변 프로그램은 단지 SHA-256 해시를 계산 합니다.
다소 오래된 시스템 (Intel® Core ™ 2 CPU 6400 @ 2.13 GHz)의 결과 :
avg time (ms) with -dJUNKOPS: 1822.84 ms
avg time (ms) without: 1836.44 ms
프로그램은 루프에서 25 번 실행되었으며 매번 실행 순서가 무작위로 변경되었습니다.
발췌 :
{$asmmode intel}
procedure example_junkop_in_sha256;
var s1, t2 : uint32;
begin
// Here are parts of the SHA-256 algorithm, in Pascal:
// s0 {r10d} := ror(a, 2) xor ror(a, 13) xor ror(a, 22)
// s1 {r11d} := ror(e, 6) xor ror(e, 11) xor ror(e, 25)
// Here is how I translated them (side by side to show symmetry):
asm
MOV r8d, a ; MOV r9d, e
ROR r8d, 2 ; ROR r9d, 6
MOV r10d, r8d ; MOV r11d, r9d
ROR r8d, 11 {13 total} ; ROR r9d, 5 {11 total}
XOR r10d, r8d ; XOR r11d, r9d
ROR r8d, 9 {22 total} ; ROR r9d, 14 {25 total}
XOR r10d, r8d ; XOR r11d, r9d
// Here is the extraneous operation that I removed, causing a speedup
// s1 is the uint32 variable declared at the start of the Pascal code.
//
// I had cleaned up the code, so I no longer needed this variable, and
// could just leave the value sitting in the r11d register until I needed
// it again later.
//
// Since copying to RAM seemed like a waste, I removed the instruction,
// only to discover that the code ran slower without it.
{$IFDEF JUNKOPS}
MOV s1, r11d
{$ENDIF}
// The next part of the code just moves on to another part of SHA-256,
// maj { r12d } := (a and b) xor (a and c) xor (b and c)
mov r8d, a
mov r9d, b
mov r13d, r9d // Set aside a copy of b
and r9d, r8d
mov r12d, c
and r8d, r12d { a and c }
xor r9d, r8d
and r12d, r13d { c and b }
xor r12d, r9d
// Copying the calculated value to the same s1 variable is another speedup.
// As far as I can tell, it doesn't actually matter what register is copied,
// but moving this line up or down makes a huge difference.
{$IFDEF JUNKOPS}
MOV s1, r9d // after mov r12d, c
{$ENDIF}
// And here is where the two calculated values above are actually used:
// T2 {r12d} := S0 {r10d} + Maj {r12d};
ADD r12d, r10d
MOV T2, r12d
end
end;
직접 해보십시오.
코드 를 직접 사용하려면 GitHub에서 온라인 상태 입니다.
내 질문 :
- 레지스터의 내용을 쓸모없이 RAM에 복사 하면 성능이 향상 되는 이유는 무엇 입니까?
- 왜 같은 쓸모없는 명령이 일부 회선의 속도를 높이고 다른 회선의 속도를 줄이겠습니까?
- 이 동작은 컴파일러가 예측할 수있는 것입니까?