소장 할 만 한 9 가지 코드 운행 효율 을 높이 는 팁(추천)
1.프로그램 계 산 량 줄 이기
1.1 예시 코드
for (i = 0; i < n; i++) {
int ni = n*i;
for (j = 0; j < n; j++)
a[ni + j] = b[j];
}
1.2 분석 코드*8195°코드 는 위 에서 보 듯 이 외부 순환 이 실 행 될 때마다 우 리 는 곱셈 계산 을 해 야 한다.i = 0,ni = 0;i = 1,ni = n;i = 2,ni = 2n。따라서 우 리 는 곱셈 을 덧셈 으로 바 꾸 고 n 을 보폭 으로 하면 외부 순환 의 코드 량 을 줄 일 수 있다.
1.3 개선 코드
int ni = 0;
for (i = 0; i < n; i++) {
for (j = 0; j < n; j++)
a[ni + j] = b[j];
ni += n; //
}
컴퓨터 에서 덧셈 명령 은 곱셈 명령 보다 훨씬 빠르다.2.코드 의 공통 부분 추출
2.1 예시 코드
『8195』상상 해 보 세 요.우 리 는 그림 을 2 차원 배열 로 표시 하고 배열 요 소 는 픽 셀 점 을 대표 합 니 다.우 리 는 픽 셀 을 정 한 동,남,서,북 네 이웃 의 총 화 를 얻 고 싶다.그들의 평균치 나 그들의 합 을 구하 라.코드 는 아래 와 같다.
up = val[(i-1)*n + j ];
down = val[(i+1)*n + j ];
left = val[i*n + j-1];
right = val[i*n + j+1];
sum = up + down + left + right;
2.2 분석 코드상기 코드 를 컴 파일 한 후에 어 셈 블 리 코드 를 다음 과 같이 얻 을 수 있 습 니 다.3,4,5 줄 에 주의 하고 3 곱 하기 n 의 곱셈 연산 이 있 습 니 다.우 리 는 위의 up 과 down 을 펼 친 후에 네 칸 의 표현 식 에 i*n+j 가 있 는 것 을 발견 할 수 있 습 니 다.따라서 공공 부분 을 추출 한 다음 가감 연산 을 통 해 up,down 등의 값 을 나 눌 수 있다.
leaq 1(%rsi), %rax # i+1
leaq -1(%rsi), %r8 # i-1
imulq %rcx, %rsi # i*n
imulq %rcx, %rax # (i+1)*n
imulq %rcx, %r8 # (i-1)*n
addq %rdx, %rsi # i*n+j
addq %rdx, %rax # (i+1)*n+j
addq %rdx, %r8 # (i-1)*n+j
2.3 개선 코드
long inj = i*n + j;
up = val[inj - n];
down = val[inj + n];
left = val[inj - 1];
right = val[inj + 1];
sum = up + down + left + right;
개 선 된 코드 의 어 셈 블 리 는 다음 과 같다.컴 파일 후 곱셈 이 하나 밖 에 없다.시계 주 기 를 6 개 줄 였 다.
imulq %rcx, %rsi # i*n
addq %rdx, %rsi # i*n+j
movq %rsi, %rax # i*n+j
subq %rcx, %rax # i*n+j-n
leaq (%rsi,%rcx), %rcx # i*n+j+n
...
GCC 컴 파 일 러 에 있어 컴 파 일 러 는 서로 다른 최적화 등급 에 따라 서로 다른 최적화 방식 을 가 질 수 있 고 상기 최적화 작업 을 자동 으로 완성 할 수 있다.다음은 우리 가 소개 하 겠 습 니 다.그것 은 반드시 우리 가 수 동 으로 최적화 해 야 합 니 다.3.순환 중 저 효과 코드 제거
3.1 예시 코드
『8195』프로그램 은 문제 가 없어 보 입 니 다.일반적인 대소 문자 변환 코드 입 니 다.그런데 왜 문자열 의 입력 길이 가 길 어 지면 서 코드 의 실행 시간 이 지수 적 으로 증가 합 니까?
void lower1(char *s)
{
size_t i;
for (i = 0; i < strlen(s); i++)
if (s[i] >= 'A' && s[i] <= 'Z')
s[i] -= ('A' - 'a');
}
3.2 분석 코드*8195:8195:그러면 코드 를 테스트 하고 일련의 문자열 을 입력 합 니 다.
*8195:8195:입력 문자열 의 길이 가 100000 보다 낮 을 때 프로그램 운행 시간 차이 가 크 지 않 습 니 다.그러나 문자열 의 길이 가 증가 함 에 따라 프로그램의 운행 시간 이 지수 일 때 증가한다.
코드 를 goto 형식 으로 바 꿔 보 겠 습 니 다.
void lower1(char *s)
{
size_t i = 0;
if (i >= strlen(s))
goto done;
loop:
if (s[i] >= 'A' && s[i] <= 'Z')
s[i] -= ('A' - 'a');
i++;
if (i < strlen(s))
goto loop;
done:
}
이상 코드 는 초기 화(3 줄),테스트(4 줄),업데이트(9,10 줄)세 부분 으로 나 뉜 다.초기 화 는 한 번 만 실 행 됩 니 다.하지만 테스트 와 업 데 이 트 는 매번 실 행 됩 니 다.매번 순환 을 진행 할 때마다 strlen 을 한 번 호출 합 니 다.*8195:8195:strlen 함수 의 소스 코드 가 문자열 의 길 이 를 어떻게 계산 하 는 지 보 겠 습 니 다.
size_t strlen(const char *s)
{
size_t length = 0;
while (*s != '\0') {
s++;
length++;
}
return length;
}
*8195°strlen 함수 가 문자열 의 길 이 를 계산 하 는 원 리 는'\0'을 만 날 때 까지 문자열 을 옮 겨 다 니 는 것 입 니 다.따라서 strlen 함수 의 시간 복잡 도 는 O(N)이다.lower 1 에서 길이 가 N 인 문자열 의 경우 strlen 의 호출 횟수 는 N,N-1,N-2...1 입 니 다.선형 시간의 함수 호출 N 회 에 대해 그 시간 복잡 도 는 O(N2)에 가깝다.3.3 개선 코드
『8195』순환 중 에 나타 나 는 이러한 불필요 한 호출 에 대해 우 리 는 이 를 순환 밖으로 이동 시 킬 수 있다.계산 결 과 를 순환 에 사용 합 니 다.개 선 된 코드 는 다음 과 같다.
void lower2(char *s)
{
size_t i;
size_t len = strlen(s);
for (i = 0; i < len; i++)
if (s[i] >= 'A' && s[i] <= 'Z')
s[i] -= ('A' - 'a');
}
다음 그림 과 같이 두 함 수 를 비교 합 니 다.lower 2 함수 의 실행 시간 이 뚜렷하게 향상 되 었 습 니 다.4.불필요 한 메모리 인용 제거
4.1 예시 코드
『8195』다음 코드 는 a 배열 의 모든 줄 의 모든 요소 와 b[i]에 존재 하 는 것 을 계산 하 는 역할 을 합 니 다.
void sum_rows1(double *a, double *b, long n) {
long i, j;
for (i = 0; i < n; i++) {
b[i] = 0;
for (j = 0; j < n; j++)
b[i] += a[i*n + j];
}
}
4.2 분석 코드어 셈 블 리 코드 는 다음 과 같다.
# sum_rows1 inner loop
.L4:
movsd (%rsi,%rax,8), %xmm0 # %xmm0
addsd (%rdi), %xmm0 # %xmm0
movsd %xmm0, (%rsi,%rax,8) # %xmm0 , b[i]
addq $8, %rdi
cmpq %rcx, %rdi
jne .L4
이것 은 매번 순환 할 때마다 메모리 에서 b[i]를 읽 은 다음 에 b[i]를 메모리 에 다시 써 야 한 다 는 것 을 의미한다.b[i] += b[i] + a[i*n + j]; 사실 매번 순환 이 시 작 될 때마다 b[i]는 지난번 의 값 입 니 다.왜 매번 메모리 에서 읽 고 다시 써 야 합 니까?4.3 개선 코드
/* Sum rows is of n X n matrix a
and store in vector b */
void sum_rows2(double *a, double *b, long n) {
long i, j;
for (i = 0; i < n; i++) {
double val = 0;
for (j = 0; j < n; j++)
val += a[i*n + j];
b[i] = val;
}
}
총집 은 아래 와 같다.
# sum_rows2 inner loop
.L10:
addsd (%rdi), %xmm0 # FP load + add
addq $8, %rdi
cmpq %rax, %rdi
jne .L10
*8195°개 선 된 코드 는 중간 결 과 를 저장 하기 위해 임시 변 수 를 도입 하여 마지막 값 이 계 산 될 때 만 결 과 를 배열 이나 전체 변수 에 저장 합 니 다.5.불필요 한 호출 줄 이기
5.1 예시 코드
예 를 들 어 우 리 는 배열 과 배열 의 길 이 를 포함 하 는 구조 체 를 정의 합 니 다.주로 배열 의 접근 을 방지 하기 위해 서 입 니 다.datat 는 int,long 등 유형 일 수 있 습 니 다.구체 적 으로 다음 과 같다.
typedef struct{
size_t len;
data_t *data;
} vec;
get_vec_element 함수 의 역할 은 data 배열 의 요 소 를 옮 겨 다 니 며 val 에 저장 하 는 것 입 니 다.
int get_vec_element (*vec v, size_t idx, data_t *val)
{
if (idx >= v->len)
return 0;
*val = v->data[idx];
return 1;
}
다음 코드 를 예 로 들 어 최적화 프로그램 을 시작 하 겠 습 니 다.
void combine1(vec_ptr v, data_t *dest)
{
long int i;
*dest = NULL;
for (i = 0; i < vec_length(v); i++) {
data_t val;
get_vec_element(v, i, &val);
*dest = *dest * val;
}
}
5.2 분석 코드get_vec_element 함수 의 역할 은 다음 요 소 를 가 져 오 는 것 입 니 다.getvec_element 함수 에 서 는 매번 순환 할 때마다 v->len 과 비교 하여 경 계 를 넘 지 않도록 해 야 합 니 다.경계 검 사 를 하 는 것 은 좋 은 습관 이지 만 매번 진행 하면 효율 이 떨어진다.
5.3 개선 코드
『8195』우 리 는 벡터 길 이 를 구 하 는 코드 를 순환 체 외 로 옮 길 수 있 으 며 추상 적 인 데이터 형식 에 함수 get 을 추가 할 수 있 습 니 다.vec_start。이 함 수 는 배열 의 시작 주 소 를 되 돌려 줍 니 다.이렇게 하면 순환 체 에서 함수 호출 이 없 이 배열 에 직접 접근 합 니 다.
data_t *get_vec_start(vec_ptr v)
{
return v->data;
}
void combine2 (vec_ptr v, data_t *dest)
{
long i;
long length = vec_length(v);
data_t *data = get_vec_start(v);
*dest = NULL;
for (i=0;i < length;i++)
{
*dest = *dest * data[i];
}
}
6.순환 전개6.1 예시 코드
우 리 는 combine 2 의 코드 를 개선 합 니 다.
6.2 분석 코드
『8195』순환 전 개 는 매번 반복 적 으로 계 산 된 요소 의 수량 을 증가 시 켜 순환 의 교체 횟수 를 감소 하 는 것 이다.
6.3 개선 코드
void combine3(vec_ptr v, data_t *dest)
{
long i;
long length = vec_length(v);
long limit = length-1;
data_t *data = get_vec_start(v);
data_t acc = NULL;
/* */
for (i = 0; i < limit; i+=2) {
acc = (acc * data[i]) * data[i+1];
}
/* */
for (; i < length; i++) {
acc = acc * data[i];
}
*dest = acc;
}
*8195:8195:개 선 된 코드 에서 첫 번 째 순환 은 배열 의 두 요 소 를 처리 합 니 다.즉,매번 교체 할 때마다 순환 색인 i 에 2 를 추가 하고 한 번 의 교체 에서 배열 요소 i 와 i+1 에 대해 합병 연산 을 사용 하 는 것 이다.보통 우 리 는 이런 걸 2 라 고 부른다.×1.순환 전개,이런 변 화 는 순환 비용 의 영향 을 줄 일 수 있다.접근 할 때 경 계 를 넘 지 않도록 주의 하 세 요.limit,n 개 요 소 를 정확하게 설정 하고 보통 한계 n-1 을 설정 합 니 다.
7.누적 변수,다 중 병렬
7.1 예시 코드
우 리 는 combine 3 의 코드 를 개선 합 니 다.
7.2 분석 코드
*8195:8195:결합 가능 하고 교환 가능 한 합병 연산 에 있어 정수 덧셈 이나 곱셈 등 우 리 는 한 조 의 합병 연산 을 두 개 이상 의 부분 으로 나 누고 마지막 에 합병 결 과 를 통 해 성능 을 향상 시 킬 수 있다.
특히 주의:부동 소수점 을 쉽게 결합 하지 마 세 요.부동 소수점 인 코딩 형식 은 다른 정수 등 과 다르다.
7.3 개선 코드
void combine4(vec_ptr v, data_t *dest)
{
long i;
long length = vec_length(v);
long limit = length-1;
data_t *data = get_vec_start(v);
data_t acc0 = 0;
data_t acc1 = 0;
/* , */
for (i = 0; i < limit; i+=2) {
acc0 = acc0 * data[i];
acc1 = acc1 * data[i+1];
}
/* */
for (; i < length; i++) {
acc0 = acc0 * data[i];
}
*dest = acc0 * acc1;
}
*8195°상기 코드 는 두 번 의 순환 으로 전개 되 었 고 매번 교체 할 때마다 더 많은 요 소 를 합병 시 켰 으 며 두 가지 병행 을 사용 하여 색인 값 을 짝수 로 하 는 요 소 를 변수 acc0 에 누적 시 켰 으 며 색인 값 이 홀수 인 요 소 는 변수 acc1 에 누적 되 었 습 니 다.그래서 우 리 는 그것 을'2'라 고 부른다.×2 순환 전개.운용×2 순환 전개.여러 개의 누적 변 수 를 유지 함으로써 이런 방법 은 여러 개의 기능 단원 과 그들의 유수 선 능력 을 이용 했다8.재결합 변환
8.1 예시 코드
우 리 는 combine 3 의 코드 를 개선 합 니 다.
8.2 분석 코드
여기까지 사실 코드 의 성능 은 이미 기본적으로 극한 에 가 까 워 졌 다.아무리 많은 순환 을 해서 성능 을 향상 시 켜 도 뚜렷 하지 않다.우 리 는 생각 을 바 꿔 야 합 니 다.combine 3 코드 중 12 줄 의 코드 를 주의 하 십시오.우 리 는 벡터 요소 의 합병 순 서 를 바 꿀 수 있 습 니 다(부동 소수점 은 적용 되 지 않 습 니 다).이전 combine 3 코드 를 다시 결합 하 는 관건 적 인 경 로 는 다음 그림 과 같다.
8.3 개선 코드
void combine7(vec_ptr v, data_t *dest)
{
long i;
long length = vec_length(v);
long limit = length-1;
data_t *data = get_vec_start(v);
data_t acc = IDENT;
/* Combine 2 elements at a time */
for (i = 0; i < limit; i+=2) {
acc = acc * (data[i] * data[i+1]);
}
/* Finish any remaining elements */
for (; i < length; i++) {
acc = acc * data[i];
}
*dest = acc;
}
*8195°8195°재 결합 변환 은 계산 에서 관건 적 인 경로 에서 조작 하 는 수량 을 줄 일 수 있다.이런 방법 은 병행 할 수 있 는 조작 수량 을 증가 하고 기능 단원 의 흐름 선 능력 을 잘 이용 하여 더욱 좋 은 성능 을 얻 을 수 있다.다시 결합 한 후 관건 적 인 경 로 는 다음 과 같다.9 조건 전송 스타일 의 코드
9.1 예시 코드
void minmax1(long a[],long b[],long n){
long i;
for(i = 0;i,n;i++){
if(a[i]>b[i]){
long t = a[i];
a[i] = b[i];
b[i] = t;
}
}
}
9.2 분석 코드현대 프로세서 의 파이프라인 성능 은 프로세서 의 작업 이 현재 실행 중인 명령 보다 훨씬 앞 선다.프로세서 의 분기 예측 은 비교 명령 을 만 났 을 때 다음 단계 가 어디로 넘 어 갈 지 예측 합 니 다.예측 이 틀 리 면,다시 분기 도약 의 제자리 로 돌아 가 야 한다.분기 예측 오 류 는 프로그램의 실행 효율 에 심각 한 영향 을 줄 수 있다.따라서 프로세서 의 정확도 가 높 아 지 는 코드,즉 조건 전송 명령 을 사용 해 야 합 니 다.우 리 는 조건 조작 으로 값 을 계산 한 다음 에 이 값 으로 프로그램 상 태 를 업데이트 합 니 다.구체 적 으로 개 선 된 코드 와 같 습 니 다.
9.3 개선 코드
void minmax2(long a[],long b[],long n){
long i;
for(i = 0;i,n;i++){
long min = a[i] < b[i] ? a[i]:b[i];
long max = a[i] < b[i] ? b[i]:a[i];
a[i] = min;
b[i] = max;
}
}
『8195』는 원래 코드 의 네 번 째 줄 에서 a[i]와 b[i]를 비교 하고 다음 작업 을 해 야 한다.이런 결 과 는 매번 예측 해 야 한다.개 선 된 코드 가 이 함 수 를 실현 하 는 것 은 각 위치 i 의 최대 값 과 최소 값 을 계산 한 다음 에 이 값 을 각각 a[i]와 b[i]에 부여 하 는 것 이지 분기 예측 을 하 는 것 이 아 닙 니 다.10.총화
『8195』우 리 는 코드 효율 을 향상 시 키 는 몇 가지 기 교 를 소개 했다.어떤 것 은 컴 파일 러 가 자동 으로 최적화 할 수 있 고 어떤 것 은 우리 가 스스로 실현 해 야 하 는 것 이다.지금 총 결 은 아래 와 같다.
연속 함수 호출 제거.가능 할 때 계산 을 순환 밖으로 옮 깁 니 다.더 큰 효율 을 얻 기 위해 선택 적 인 타협 프로그램의 모듈 성 을 고려 하 다.
불필요 한 메모리 참조 제거.중간 결 과 를 저장 하기 위해 임시 변 수 를 도입 합 니 다.마지막 값 을 계산 할 때 만 결 과 를 배열 이나 전역 변수 에 저장 합 니 다.
순환 을 전개 하고 비용 을 낮 추 며 진일보 한 최 적 화 를 가능 하 게 한다.
예 를 들 어 여러 개의 누적 변수 와 재 결합 등 기술 을 사용 하여 명령 급 을 향상 시 키 는 방법 을 찾 아 병행 한다.
기능 적 인 스타일 로 조건 을 재 작성 하여 컴 파일 을 조건 데이터 로 전송 합 니 다.
소장 할 만 한 코드 운행 효율 을 높이 는 9 가지 팁(추천)에 관 한 이 글 은 여기까지 소개 되 었 습 니 다.더 많은 관련 코드 운행 효율 을 높이 는 내용 은 우리 의 이전 글 을 검색 하거나 아래 의 관련 글 을 계속 찾 아 보 세 요.앞으로 많은 응원 부탁드립니다!