Analysis C Operators


3 분 소요

기본 연산들의 operation 속도를 측정-비교해보자

사칙연산

연산 값이 1인경우

MUL = DIV > ADD > SUB 순으로 속도가 빠르다.

add: 35972.570000
div: 23553.610000
mul: 23303.400000
sub: 41171.720000

ADD와 SUB는 연산 수는 같지만 연산 자체의 속도 차이가 있는 것을 알 수 있다.

MUL과 DIV의 경우 최적화를 하여 연산 자체를 안한다. 아래 핵심 코드만 보면 연산 자체를 하지 않는 것을 알 수 있다.

add:
	addl	$1, -4(%rbp)
	addl	$1, -8(%rbp)
sub:
	subl	$1, -4(%rbp)
	addl	$1, -8(%rbp)
mul:
	addl	$1, -8(%rbp)
div:
	addl	$1, -8(%rbp)

연산 값이 2인경우

ADD = MUL > SUB > DIV 순으로 속도가 빠르다.

add: 35999.910000
div: 69783.050000
mul: 36125.900000
sub: 41972.840000

MUL의 경우 shift 연산을 통해 곱셈을 하고 있다.

DIV의 경우 shift 연산을 통해 나눗셈을 하고 있다. -3인 경우 2로 나누면 -1, 3인 경우 2로 나누면 1이 나와야 한다. 그런데 -3을 쉬프트 연산만 하면 -2가 되기 때문에 기존 값에 1을 더해주고(음수이니 빼는 효과) 쉬프트 연산을 하고 있다.

add:
	addl	$2, -4(%rbp)
	addl	$1, -8(%rbp)
sub:
	subl	$2, -4(%rbp)
	addl	$1, -8(%rbp)
mul:
	sall	-4(%rbp)
	addl	$1, -8(%rbp)
div:
	movl	-4(%rbp), %eax
	movl	%eax, %edx
	shrl	$31, %edx
	addl	%edx, %eax
	sarl	%eax
	movl	%eax, -4(%rbp)
	addl	$1, -8(%rbp)

연산 값이 3인경우

ADD > SUB > MUL > DIV 순으로 속도가 빠르다.

이제는 DIV 속도가 현저히 느려지기 시작한다.

add: 37361.260000
div: 143092.990000
mul: 46847.060000
sub: 41042.840000

MUL을 보면 흥미롭다. 3을 곱하는 것을 2번의 덧셈 연산으로 처리하고 있다. a+a+a = 3a

DIV의 경우에는 굉장히 복잡해졌다. 굉장히 흥미로운데, 찾아보니 3으로 나누는 것을 효율적으로 흉내내기위한 기법(Magic Division)이라고 한다. 매직 넘버 1431655766 (= 0x55555556)를 64비트 정수 곱(imulq)으로 곱한 값의 상위 32비트가 3으로 나눈 결과라고 한다.;;; 위에서 언급한 것처럼 sarl, movl, subl은 음수인 경우 0에 가깝게 만들기 위한(소수점 버리기) 용이다.

add:
	addl	$3, -4(%rbp)
	addl	$1, -8(%rbp)
sub:
	subl	$3, -4(%rbp)
	addl	$1, -8(%rbp)
mul:
	movl	-4(%rbp), %edx
	movl	%edx, %eax
	addl	%eax, %eax
	addl	%edx, %eax
	movl	%eax, -4(%rbp)
	addl	$1, -8(%rbp)
div:
	movl	-4(%rbp), %eax
	movslq	%eax, %rdx
	imulq	$1431655766, %rdx, %rdx
	shrq	$32, %rdx
	sarl	$31, %eax
	movl	%eax, %ecx
	movl	%edx, %eax
	subl	%ecx, %eax
	movl	%eax, -4(%rbp)
	addl	$1, -8(%rbp)

연산 값이 4인경우

ADD = MUL > SUB > DIV 순으로 속도가 빠르다.

MUL과 DIV의 경우에 처리 시간이 다시 짧아졌다. 2의 제곱수를 곱하거나 나누는 것은 쉬프트 연산으로 처리하기 때문이다.

add: 36099.800000
div: 101877.350000
mul: 36600.710000
sub: 40781.050000

MUL은 ADD나 SUB처럼 하나의 연산으로 처리하고 있다. ADD 연산과 동일한 처리시간을 갖는 것을 알 수 있다.

DIV도 쉬프트 연산으로 처리하는데 흥미로운 점은 leal(eax에 3을 더함)을 덧셈으로 사용하여 쉬프트 시 나누어 떨어지지 않는 값을 버리기 위해 음수인 경우에는 기존 값에 3을 더하고 있다. 이로써 -6을 -1으로 만들 수 있다. 그렇지 않다면 -2가 나오게 된다.

add:
	addl	$4, -4(%rbp)
	addl	$1, -8(%rbp)
sub:
	subl	$4, -4(%rbp)
	addl	$1, -8(%rbp)
mul:
	sall	$2, -4(%rbp)
	addl	$1, -8(%rbp)
div:
	movl	-4(%rbp), %eax
	leal	3(%rax), %edx
	testl	%eax, %eax
	cmovs	%edx, %eax
	sarl	$2, %eax
	movl	%eax, -4(%rbp)
	addl	$1, -8(%rbp)

연산 값이 5인경우

ADD > SUB > MUL > DIV 순으로 속도가 빠르다.

add: 36382.720000
div: 163627.380000
mul: 49184.050000
sub: 40298.730000

MUL의 경우에는 쉬프트 연산으로 4를 곱하고 1을 더하는 것으로 처리하고 있다. 곱셈 연산이 앵간히 느린가보다. 쉬프트+덧셈으로 처리하는 것이 더 빠르다는 뜻일테니.

DIV의 경우에는 3으로 나누는 것과 마찬가지로 Magic Division을 사용하고 있다. 다만 매직 넘버는 1717986919 (= 0x66666667)이다.

add:
	addl	$5, -4(%rbp)
	addl	$1, -8(%rbp)
sub:
	subl	$5, -4(%rbp)
	addl	$1, -8(%rbp)
mul:
	movl	-4(%rbp), %edx
	movl	%edx, %eax
	sall	$2, %eax
	addl	%edx, %eax
	movl	%eax, -4(%rbp)
	addl	$1, -8(%rbp)
div:
	movl	-4(%rbp), %eax
	movslq	%eax, %rdx
	imulq	$1717986919, %rdx, %rdx
	shrq	$32, %rdx
	sarl	%edx
	sarl	$31, %eax
	movl	%eax, %ecx
	movl	%edx, %eax
	subl	%ecx, %eax
	movl	%eax, -4(%rbp)
	addl	$1, -8(%rbp)

실행 코드

#include <time.h>
#include <stdio.h>

#define MEASURE(__t, __n, __func) printf("%s: %lf\n", (#__func), measure(__t, __n, (__func)))

#define NUM 5

int add(int n) {
	int i, j;
	int tmp = 0;

	for (i = 0; i < n; i++) {
		tmp += NUM;
	}
}

int sub(int n) {
	int i, j;
	int tmp = 0;

	for (i = 0; i < n; i++) {
		tmp -= NUM;
	}
}

int mul(int n) {
	int i, j;
	int tmp = 0;

	for (i = 0; i < n; i++) {
		tmp *= NUM;
	}
}

int div(int n) {
	int i, j;
	int tmp = 0;

	for (i = 0; i < n; i++) {
		tmp /= NUM;
	}
}

double measure(int t, int n, int (*cb)(int)) {
	int i;
	double sum;
	clock_t start, end;

	for (i = 0; i < t; i++) {
		start = clock();
		cb(n);
		end = clock();
		sum += (double)(end - start);
	}
	return sum / t;
}

int main() {
	int t = 100;
	int n = 100000000;

	MEASURE(t, n, add);
	MEASURE(t, n, div);
	MEASURE(t, n, mul);
	MEASURE(t, n, sub);


	return 0;
}

관련 내용에 대한 질문이나 태클을 환영합니다. 댓글 남겨주세요.



태그:

카테고리:

작성:

업데이트:

댓글남기기