[英]How to find the largest element of an array of known size?
我需要在數組中找到包含正好16個整數的最大元素。 我正在考慮兩種可能的實現方式。 首先,明智的實施:
int largest = array[0];
for (int i = 1; i < 16; i++) {
const int val = array[i];
if (val > largest) {
largest = val;
}
}
然后有一個稍微瘋狂的實現,利用了數組大小已知的事實:
const int max_value =
max(
max(
max(
max(array[0], array[1]),
max(array[2], array[3])),
max(
max(array[4], array[5]),
max(array[6], array[7]))),
max(
max(
max(array[8], array[9])
max(array[10], array[11])),
max(
max(array[12], array[13])
max(array[14], array[15]))));
哪個更好實現? max
通常是在硬件中實現的嗎?
讓我們編譯它們,看看我們得到了什么!
首先,AFAIK,C標准中沒有定義“max”函數/宏。 所以我添加了一個(看起來很復雜,因為它避免了對其輸入的雙重評估)。
#define max(a,b) ({ \
const __typeof__ (a) _a = (a); \
const __typeof__ (b) _b = (b); \
_a > _b ? _a : _b; \
})
int __attribute__ ((noinline)) test1(const int* array) {
int largest = array[0];
for (int i = 1; i < 16; i++) {
const int val = array[i];
if (val > largest) {
largest = val;
}
}
return largest;
}
int __attribute__ ((noinline)) test2(const int* array) {
const int max_value =
max(
max(
max(
max(array[0], array[1]),
max(array[2], array[3])),
max(
max(array[4], array[5]),
max(array[6], array[7]))),
max(
max(
max(array[8], array[9]),
max(array[10], array[11])),
max(
max(array[12], array[13]),
max(array[14], array[15]))));
return max_value;
}
我的gcc版本,在談到優化時是相關的:
tmp$ gcc --version
gcc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4
Copyright (C) 2013 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-O2
用於優化, -S
用於輸出程序集, -o -
用於輸出到stdout。
tmp$ gcc -std=c99 -O2 -S test.c -o -
.file "test.c"
.text
.p2align 4,,15
.globl test1
.type test1, @function
test1:
.LFB0:
.cfi_startproc
movl (%rdi), %eax
xorl %edx, %edx
.p2align 4,,10
.p2align 3
.L3:
movl 4(%rdi,%rdx), %ecx
cmpl %ecx, %eax
cmovl %ecx, %eax
addq $4, %rdx
cmpq $60, %rdx
jne .L3
rep ret
.cfi_endproc
.LFE0:
.size test1, .-test1
.p2align 4,,15
.globl test2
.type test2, @function
test2:
.LFB1:
.cfi_startproc
movl (%rdi), %edx
cmpl %edx, 4(%rdi)
cmovge 4(%rdi), %edx
movl 8(%rdi), %eax
cmpl %eax, %edx
cmovl %eax, %edx
movl 12(%rdi), %eax
cmpl %eax, %edx
cmovl %eax, %edx
movl 16(%rdi), %eax
cmpl %eax, %edx
cmovl %eax, %edx
movl 20(%rdi), %eax
cmpl %eax, %edx
cmovl %eax, %edx
movl 24(%rdi), %eax
cmpl %eax, %edx
cmovl %eax, %edx
movl 28(%rdi), %eax
cmpl %eax, %edx
cmovl %eax, %edx
movl 32(%rdi), %eax
cmpl %eax, %edx
cmovl %eax, %edx
movl 36(%rdi), %eax
cmpl %eax, %edx
cmovl %eax, %edx
movl 40(%rdi), %eax
cmpl %eax, %edx
cmovl %eax, %edx
movl 44(%rdi), %eax
cmpl %eax, %edx
cmovl %eax, %edx
movl 48(%rdi), %eax
cmpl %eax, %edx
cmovl %eax, %edx
movl 52(%rdi), %eax
cmpl %eax, %edx
cmovl %eax, %edx
movl 56(%rdi), %eax
cmpl %eax, %edx
cmovl %eax, %edx
movl 60(%rdi), %eax
cmpl %eax, %edx
cmovge %edx, %eax
ret
.cfi_endproc
.LFE1:
.size test2, .-test2
.ident "GCC: (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4"
.section .note.GNU-stack,"",@progbits
好吧,所以test2()
肯定看起來更長。 但是,它根本不分支。 並且每個元素只有~3條指令(內存加載,比較,條件移動)。 test1()
有6條指令(內存加載,比較,條件移動,循環計數器遞增,循環計數器比較,條件分支)。 test1
很多分支,這可能很麻煩(取決於你的架構的分支預測有多好)。 另一方面, test2
增加了代碼大小,這必然會從指令緩存中推出其他東西。 test2
有很多數據危險(好吧,還有test1
......) - 也許我們可以重寫它以使用一些額外的寄存器來減少管道停頓的數量?
因此,正如您現在可能已經看到的那樣,這不是一個容易回答的問題。
唯一真正知道的方法就是衡量它。 即使這樣,它也會根據每個CPU模型的內部實現/優化/緩存大小而有所不同。
所以我寫了一個小基准:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <stdint.h>
#define N (1000000)
int main() {
printf(" %12s %12s %12s %12s\n", "test1 time", "test2 time", "test1 out", "test2 out");
int* data = malloc(N * 16 * sizeof(int));
srand(1);
for (int i=0; i<16*N; ++i) {
data[i] = rand();
}
const int* a;
struct timespec t1, t2, t3;
for (int attempt=0; attempt<10; ++attempt) {
uint32_t sum1 = 0;
uint32_t sum2 = 0;
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t1);
a = data;
for (int i=0; i<N; ++i) {
sum1 += test1(a);
a += 16;
}
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t2);
a = data;
for (int i=0; i<N; ++i) {
sum2 += test2(a);
a += 16;
}
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &t3);
uint64_t nanos1 = (t2.tv_sec - t1.tv_sec) * 1000000000L + (t2.tv_nsec - t1.tv_nsec);
uint64_t nanos2 = (t3.tv_sec - t2.tv_sec) * 1000000000L + (t3.tv_nsec - t2.tv_nsec);
printf("%2d: %12lu %12lu %12u %12u\n", attempt+1, nanos1, nanos2, sum1, sum2);
}
return 0;
}
結果如下:
tmp$ gcc -std=gnu99 -O2 test.c -o test
tmp$ ./test
test1 time test2 time test1 out test2 out
1: 16251659 10431322 4190722540 4190722540
2: 16796884 10639081 4190722540 4190722540
3: 16443265 10314624 4190722540 4190722540
4: 17194795 10337678 4190722540 4190722540
5: 16966405 10380047 4190722540 4190722540
6: 16803840 10556222 4190722540 4190722540
7: 16795989 10871508 4190722540 4190722540
8: 16389862 11511950 4190722540 4190722540
9: 16304850 11704787 4190722540 4190722540
10: 16309371 11269446 4190722540 4190722540
tmp$ gcc -std=gnu99 -O3 test.c -o test
tmp$ ./test
test1 time test2 time test1 out test2 out
1: 9090364 8813462 4190722540 4190722540
2: 8745093 9394730 4190722540 4190722540
3: 8942015 9839356 4190722540 4190722540
4: 8849960 8834056 4190722540 4190722540
5: 9567597 9195950 4190722540 4190722540
6: 9130245 9115883 4190722540 4190722540
7: 9680596 8930225 4190722540 4190722540
8: 9268440 9998824 4190722540 4190722540
9: 8851503 8960392 4190722540 4190722540
10: 9767021 8875165 4190722540 4190722540
tmp$ gcc -std=gnu99 -Os test.c -o test
tmp$ ./test
test1 time test2 time test1 out test2 out
1: 17569606 10447512 4190722540 4190722540
2: 17755450 10811861 4190722540 4190722540
3: 17718714 10372411 4190722540 4190722540
4: 17743248 10378728 4190722540 4190722540
5: 18747440 10306748 4190722540 4190722540
6: 17877105 10782263 4190722540 4190722540
7: 17787171 10522498 4190722540 4190722540
8: 17771172 10445461 4190722540 4190722540
9: 17683935 10430900 4190722540 4190722540
10: 17670540 10543926 4190722540 4190722540
tmp$ gcc -std=gnu99 -O2 -funroll-loops test.c -o test
tmp$ ./test
test1 time test2 time test1 out test2 out
1: 9840366 10008656 4190722540 4190722540
2: 9826522 10529205 4190722540 4190722540
3: 10208039 10363219 4190722540 4190722540
4: 9863467 10284608 4190722540 4190722540
5: 10473329 10054511 4190722540 4190722540
6: 10298968 10520570 4190722540 4190722540
7: 9846157 10595723 4190722540 4190722540
8: 10340026 10041021 4190722540 4190722540
9: 10434750 10404669 4190722540 4190722540
10: 9982403 10592842 4190722540 4190722540
結論: 我的英特爾酷睿i7-3517U上的max()版本更快,具有4 MB高速緩存 (我不會要求更多,因為再次,結果可能會因微架構而異)。
此外, -funroll-loops
或-O3
啟用的額外激進(和安全性較低)優化確實對test1
情況產生巨大影響,基本上使它與test2
時間相等 - 甚至可能稍微好一點-funroll-loops
,但足夠接近,我們無法從我得到的數字中得出一個自信的結論。 在那里查看test1
的程序集可能會很有趣,但我會將其作為練習留給讀者。 ;)
所以,我猜答案是“它取決於”。
顯然是第一個,它更具可讀性和健壯性。 也許max()
沒有在硬件中實現。
Hare是c ++中max的實現
template <class T> const T& max (const T& a, const T& b) {
return (a<b)?b:a; // or: return comp(a,b)?b:a; for version (2)
}
而gcc-4.9.2的C max實現定義為
#define max(a,b) \
({ typeof (a) _a = (a); \
typeof (b) _b = (b); \
_a > _b ? _a : _b; })
所以,最好先使用第一個。 雖然可以考慮使用小於3的尺寸來實現第二個尺寸。
第一個顯然是最直接的實現。
盡管如此,這個問題與排序網絡的概念有關,這是一個關於排序固定大小的數據集的令人驚訝的復雜理論。
據我所知,C或GNU標准庫中沒有max或min函數。 第一個會更好用。 此外,您可以直接比較array [i]到最大值。
int largest = array[0];
for (int i = 1; i < 16; i++) {
if (array[i]>largest)
largest=array[i];
}
嘗試使用此功能:
int max_array(int a[], int count) {
int i,
max = a[0];
for (i = 1; i < count; i++) {
if (a[i] > max) {
max = a[i];
}
}
return max;
}
編輯:
對不起,沒看到你試過了。 但無論如何 - 這是更好的實現,你提出的第二個只是滔天。 我想如果你想保持你的代碼干凈,這就是你要去的地方。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.