[英]Why is Python faster than C when concatenating two strings?
目前我想比較Python和C的速度,當它們用於做字符串時。 我認為C應該提供比Python更好的性能; 但是,我得到了相反的結果。
這是C程序:
#include <unistd.h>
#include <sys/time.h>
#define L (100*1024)
char s[L+1024];
char c[2*L+1024];
double time_diff( struct timeval et, struct timeval st )
{
return 1e-6*((et.tv_sec - st.tv_sec)*1000000 + (et.tv_usec - st.tv_usec ));
}
int foo()
{
strcpy(c,s);
strcat(c+L,s);
return 0;
}
int main()
{
struct timeval st;
struct timeval et;
int i;
//printf("s:%x\nc:%x\n", s,c);
//printf("s=%d c=%d\n", strlen(s), strlen(c));
memset(s, '1', L);
//printf("s=%d c=%d\n", strlen(s), strlen(c));
foo();
//printf("s=%d c=%d\n", strlen(s), strlen(c));
//s[1024*100-1]=0;
gettimeofday(&st,NULL);
for( i = 0 ; i < 1000; i++ ) foo();
gettimeofday(&et,NULL);
printf("%f\n", time_diff(et,st));
return 0;
}
這是Python之一:
import time
s = '1'*102400
def foo():
c = s + s
#assert( len(c) == 204800 )
st = time.time()
for x in xrange(1000):
foo()
et = time.time()
print (et-st)
我得到了什么:
root@xkqeacwf:~/lab/wfaster# python cp100k.py
0.027932882309
root@xkqeacwf:~/lab/wfaster# gcc cp100k.c
root@xkqeacwf:~/lab/wfaster# ./a.out
0.061820
那有意義嗎? 或者我只是犯了任何愚蠢的錯誤?
累積評論(主要來自我)轉換成答案:
memmove()
或memcpy()
而不是strcpy()
和strcat()
什么? (我注意到strcat()
可以用strcpy()
替換,結果沒有區別 - 檢查時間可能很有趣。)另外,你沒有包含<string.h>
(或<stdio.h>
)所以你錯過了<string.h>
可能提供的任何優化! Marcus:是的,
memmove()
比strcpy()
快,比Python快,但為什么呢?memmove()
做一個字寬復制嗎?
Marcus:但是即使我使
L=L-13
,並且sizeof(s)
給出L+1024-13
,memmove()
仍然運行良好。 我的機器有一個sizeof(int)==4
。
memmove()
的代碼是高度優化的匯編程序,可能是內聯的(沒有函數調用開銷,但對於100KiB的數據,函數調用開銷很小)。 好處來自更大的動作和更簡單的循環條件。 馬庫斯:那么Python也使用
memmove()
,還是魔術?
memmove()
或memcpy()
(不同之處在於即使源和目標重疊, memmove()
正常工作;如果重疊, memcpy()
沒有義務正常工作)。 他們獲得比memmove/memcpy
更快的速度是相對不太可能的。 我修改了C代碼,為我的機器(Mac OS X 10.7.4,8 GiB 1333 MHz RAM,2.3 GHz Intel Core i7,GCC 4.7.1)生成更穩定的時序,並比較strcpy()
和strcat()
vs memcpy()
vs memmove()
。 請注意,我將循環計數從1000增加到10000以提高計時的穩定性,並且我重復整個測試(所有三種機制)10次。 可以說,定時循環計數應該增加另一個因子5-10,以便定時超過一秒。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#define L (100*1024)
char s[L+1024];
char c[2*L+1024];
static double time_diff( struct timeval et, struct timeval st )
{
return 1e-6*((et.tv_sec - st.tv_sec)*1000000 + (et.tv_usec - st.tv_usec ));
}
static int foo(void)
{
strcpy(c,s);
strcat(c+L,s);
return 0;
}
static int bar(void)
{
memcpy(c + 0, s, L);
memcpy(c + L, s, L);
return 0;
}
static int baz(void)
{
memmove(c + 0, s, L);
memmove(c + L, s, L);
return 0;
}
static void timer(void)
{
struct timeval st;
struct timeval et;
int i;
memset(s, '1', L);
foo();
gettimeofday(&st,NULL);
for( i = 0 ; i < 10000; i++ )
foo();
gettimeofday(&et,NULL);
printf("foo: %f\n", time_diff(et,st));
gettimeofday(&st,NULL);
for( i = 0 ; i < 10000; i++ )
bar();
gettimeofday(&et,NULL);
printf("bar: %f\n", time_diff(et,st));
gettimeofday(&st,NULL);
for( i = 0 ; i < 10000; i++ )
baz();
gettimeofday(&et,NULL);
printf("baz: %f\n", time_diff(et,st));
}
int main(void)
{
for (int i = 0; i < 10; i++)
timer();
return 0;
}
編譯時不會發出警告:
gcc -O3 -g -std=c99 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
-Wold-style-definition cp100k.c -o cp100k
我得到的時間是:
foo: 1.781506
bar: 0.155201
baz: 0.144501
foo: 1.276882
bar: 0.187883
baz: 0.191538
foo: 1.090962
bar: 0.179188
baz: 0.183671
foo: 1.898331
bar: 0.142374
baz: 0.140329
foo: 1.516326
bar: 0.146018
baz: 0.144458
foo: 1.245074
bar: 0.180004
baz: 0.181697
foo: 1.635782
bar: 0.136308
baz: 0.139375
foo: 1.542530
bar: 0.138344
baz: 0.136546
foo: 1.646373
bar: 0.185739
baz: 0.194672
foo: 1.284208
bar: 0.145161
baz: 0.205196
有點奇怪的是,如果我放棄“沒有警告”並省略<string.h>
和<stdio.h>
標題,就像在原始發布的代碼中一樣,我得到的時間是:
foo: 1.432378
bar: 0.123245
baz: 0.120716
foo: 1.149614
bar: 0.186661
baz: 0.204024
foo: 1.529690
bar: 0.104873
baz: 0.105964
foo: 1.356727
bar: 0.150993
baz: 0.135393
foo: 0.945457
bar: 0.173606
baz: 0.170719
foo: 1.768005
bar: 0.136830
baz: 0.124262
foo: 1.457069
bar: 0.130019
baz: 0.126566
foo: 1.084092
bar: 0.173160
baz: 0.189040
foo: 1.742892
bar: 0.120824
baz: 0.124772
foo: 1.465636
bar: 0.136625
baz: 0.139923
考慮到這些結果,它似乎比“更干凈”的代碼更快,雖然我沒有對兩組數據進行學生t檢驗,並且時間具有非常大的可變性(但我確實有像Boinc運行的東西后台的8個流程)。 在早期版本的代碼中,當它只是strcpy()
和strcat()
被測試時,效果似乎更加明顯。 我沒有解釋,如果它是真正的效果!
由於問題已經結束,我無法正確回答。 在幾乎沒有做任何事情的Mac上,我得到了這些時間:
(帶標題)
foo: 1.694667 bar: 0.300041 baz: 0.301693
foo: 1.696361 bar: 0.305267 baz: 0.298918
foo: 1.708898 bar: 0.299006 baz: 0.299327
foo: 1.696909 bar: 0.299919 baz: 0.300499
foo: 1.696582 bar: 0.300021 baz: 0.299775
(沒有標題,忽略警告)
foo: 1.185880 bar: 0.300287 baz: 0.300483
foo: 1.120522 bar: 0.299585 baz: 0.301144
foo: 1.122017 bar: 0.299476 baz: 0.299724
foo: 1.124904 bar: 0.301635 baz: 0.300230
foo: 1.120719 bar: 0.300118 baz: 0.299673
預處理器輸出( -E
標志)顯示包含標頭將strcpy
轉換為內置調用,如:
((__builtin_object_size (c, 0) != (size_t) -1) ? __builtin___strcpy_chk (c, s, __builtin_object_size (c, 2 > 1)) : __inline_strcpy_chk (c, s));
((__builtin_object_size (c+(100*1024), 0) != (size_t) -1) ? __builtin___strcat_chk (c+(100*1024), s, __builtin_object_size (c+(100*1024), 2 > 1)) : __inline_strcat_chk (c+(100*1024), s));
所以strcpy的libc版本優於gcc內置版。 (使用gdb
很容易驗證strcpy
上的斷點確實不會在strcpy()
調用中斷開,如果包含頭文件的話)
在Linux(Debian 5.0.9,amd64)上,差異似乎可以忽略不計。 生成的程序集( -S
標志)僅在包含的調試信息中有所不同。
我相信這樣做的原因是Python字符串不是以null結尾的。
在Python中,字符串長度與字符串一起存儲,允許它在連接字符串時跳過strcat()使用的隱式strlen()。
添加字符串連接直接在C中實現Python的事實可能就是原因。
編輯:好吧,現在我實際上看了C代碼,看到它使用靜態緩沖區,我也很神秘,因為我沒有看到Python如何避免動態分配,這應該慢得多......
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.