![](http://oshiete.xgoo.jp/images/v2/pc/qa/question_title.png?5a7ff87)
久しぶりに、(perlのXSのために)C言語を組んでみましたが、
以下のコードが、異常に遅いようでした。
(実際には、違う処理ですが、これでも遅くなりましたので、単なる文字コピーです)
例えでいえば、同じことをする数文字のperlの正規表現処理の数100倍の時間がかかっていました。
void hoge(char *dst, char *src, int len) {
inti;
for(i = 0; i < len; i++) {
*dst++=*src++;/* ここをなくすと、爆速になる */
}
}
※strlenで当初は試していましたが、ほとんどかわりありません。
どこが、原因でしょうか?
以下のコードは、きちんと爆速で動作してくれます。
void hoge(char *dst, char *src) {
for(; *dst++ = *src++;);
}
コンパイルオプションは
gcc -O2
または
gcc -O3
で、CPUは、x86_64でなっています。(AMD FX-4170)
処理した文字列は、約100Mバイト分になります。
宜しくお願いします。
No.6ベストアンサー
- 回答日時:
No.5の補足です。
各CPU向けの最適化について、VC++10のmemcpyについて言えば
CPUがSSE2をサポートしており、コピー元とコピー先のメモリアライメントが
16バイトで揃っていればSSE命令を利用し、それ以外でもストリング命令による
コピーが行われるなど単純にジャンプ命令でループしながらコピーするよりも
高速にメモリ転送が行われるように出来ています。
私の環境ではSSE2を使っても使わなくても0.5秒以下くらいでした。
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
CopyUp:
;
; First, see if we can use a "fast" copy SSE2 routine
; block size greater than min threshold?
cmp ecx,080h
jb Dword_align
; SSE2 supported?
cmp DWORD PTR __sse2_available,0
je Dword_align
; alignments equal?
push edi
push esi
and edi,15
and esi,15
cmp edi,esi
pop esi
pop edi
jne Dword_align
; do fast SSE2 copy, params already set
jmp _VEC_memcpy
; no return
;
; The algorithm for forward moves is to align the destination to a dword
; boundary and so we can move dwords with an aligned destination. This
; occurs in 3 steps.
;
; - move x = ((4 - Dest & 3) & 3) bytes
; - move y = ((L-x) >> 2) dwords
; - move (L - x - y*4) bytes
;
Dword_align:
test edi,11b ;U - destination dword aligned?
jnz short CopyLeadUp ;V - if we are not dword aligned already, align
shr ecx,2 ;U - shift down to dword count
and edx,11b ;V - trailing byte count
cmp ecx,8 ;U - test if small enough for unwind copy
jb short CopyUnwindUp ;V - if so, then jump
rep movsd ;N - move all of our dwords
jmp dword ptr TrailUpVec[edx*4] ;N - process trailing bytes
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
No.5
- 回答日時:
ymda さんコード提供ありがとうございます。
gcc4.2 -O3だとインデックス参照になっていて、llvm-gccだと内側のループが
memcpyになっており外側のループがカウントダウンになっています。
llvm-gccだと、最適化でかなりコードが書き換えられているようです。
memcpyはコンパイラによっては、自作関数より遅いことがあるそうですが、
大抵は各CPU向けの最適化が施されており速くなるように作られています。
インテルとかのコンパイラは各CPU向けの最適化が行われるので速く
gccは多様な環境に対応することを第一として設計されているので
個別のCPU向けの最適化はあまりないようです。
結局コンパイラ次第ってことですかね…
No.4
- 回答日時:
No.2 さんのアセンブラ出力を見る限り、
func1はd++やs++をせずにiによるインデックス参照になっており、
func2はループ自体回らず何もせずreturnするようになっており、
func3はソースコード通りのポインタ直参照となっております。
なので、func1よりfunc3の方が速くfunc2は何もしていないので
爆速かと思われます。
あと、fun1のcmpl %eax, %edxよりもfunc2のtestb %al, %alの方が
若干速いと思われるのでそのあたりも関係があるかもしれません。
ymda さんの環境で出力したアセンブラコードがあればもう少し
詳しく分かるかもしれません。
この回答への補足
/********* 書き直したソース
#define AA 100000
#include <stdlib.h>
int main(void) {
int i,j;
int len=AA;
char *dst,*savedst;
char *src,*savesrc;
dst=malloc(AA);
src=malloc(AA);
savesrc=src;
savedst=dst;
for(j = 0; j < AA; j++) {
src=savesrc;
dst=savedst;
for(i = 0; i < len; i++) {
//fputc('a');
*dst++=*src++;
}
}
}
/********* gcc4.2 -O3 (-O2も全く同じ)
.file"test.c"
.text
.p2align 4,,15
.globl main
.typemain, @function
main:
.LFB2:
pushq%rbx
.LCFI0:
movl$100000, %edi
callmalloc
movl$100000, %edi
movq%rax, %rbx
callmalloc
xorl%esi, %esi
movq%rax, %rcx
.L2:
xorl%edx, %edx
.p2align 4,,7
.L3:
movzbl(%rdx,%rcx), %eax
movb%al, (%rdx,%rbx)
addq$1, %rdx
cmpq$100000, %rdx
jne.L3
addl$1, %esi
cmpl$100000, %esi
jne.L2
popq%rbx
ret
.LFE2:
.sizemain, .-main
.section.eh_frame,"a",@progbits
.Lframe1:
.long.LECIE1-.LSCIE1
.LSCIE1:
.long0x0
.byte0x1
.string"zR"
.uleb128 0x1
.sleb128 -8
.byte0x10
.uleb128 0x1
.byte0x3
.byte0xc
.uleb128 0x7
.uleb128 0x8
.byte0x90
.uleb128 0x1
.align 8
.LECIE1:
.LSFDE1:
.long.LEFDE1-.LASFDE1
.LASFDE1:
.long.LASFDE1-.Lframe1
.long.LFB2
.long.LFE2-.LFB2
.uleb128 0x0
.byte0x4
.long.LCFI0-.LFB2
.byte0xe
.uleb128 0x10
.byte0x83
.uleb128 0x2
.align 8
.LEFDE1:
.ident"GCC: (GNU) 4.2.1 20070831 patched [FreeBSD]"
.section.note.GNU-stack,"",@progbits
/********* llvm-gcc
.file"test.c"
.text
.globlmain
.align16, 0x90
.typemain,@function
main:
.Leh_func_begin0:
pushq%r15
.Ltmp0:
pushq%r14
.Ltmp1:
pushq%rbx
.Ltmp2:
.Ltmp3:
movl$100000, %edi
callqmalloc
movq%rax, %rbx
movl$100000, %edi
callqmalloc
movl$100000, %r14d
movq%rax, %r15
.align16, 0x90
.LBB0_1:
movq%rbx, %rdi
movq%r15, %rsi
movl$100000, %edx
callqmemcpy
decl%r14d
jne.LBB0_1
popq%rbx
popq%r14
popq%r15
ret
.Ltmp4:
.sizemain, .Ltmp4-main
.Leh_func_end0:
.section.eh_frame,"a",@progbits
.LEH_frame0:
.Lsection_eh_frame0:
.Leh_frame_common0:
.Lset0 = .Leh_frame_common_end0-.Leh_frame_common_begin0
.long.Lset0
.Leh_frame_common_begin0:
.long0
.byte1
.asciz "zR"
.byte1
.byte120
.byte16
.byte1
.byte3
.byte12
.byte7
.byte8
.byte144
.byte1
.align8
.Leh_frame_common_end0:
.Lmain.eh:
.Lset1 = .Leh_frame_end0-.Leh_frame_begin0
.long.Lset1
.Leh_frame_begin0:
.Lset2 = .Leh_frame_begin0-.Leh_frame_common0
.long.Lset2
.long.Leh_func_begin0
.Lset3 = .Leh_func_end0-.Leh_func_begin0
.long.Lset3
.byte0
.byte4
.Lset4 = .Ltmp0-.Leh_func_begin0
.long.Lset4
.byte14
.byte16
.byte4
.Lset5 = .Ltmp1-.Ltmp0
.long.Lset5
.byte14
.byte24
.byte4
.Lset6 = .Ltmp2-.Ltmp1
.long.Lset6
.byte14
.byte32
.byte4
.Lset7 = .Ltmp3-.Ltmp2
.long.Lset7
.byte131
.byte4
.byte142
.byte3
.byte143
.byte2
.align8
.Leh_frame_end0:
.section".note.GNU-stack","",@progbits
.ident"GCC: (GNU) 4.2.1 (Based on Apple Inc. build 5658) (LLVM build)"
ありがとうございます。
他のコンパイラ(バージョン)も出してみました。
しかし、下のやりすぎて処理は消えている以外は、それなりに時間がかかる処理ですので、かかるのは当然ですが
(とはいっても、0.5秒もかかりません)
その処理が、gcc4.2 (-O3)ですと、20秒以上待たされるようです。
ちなみに、どのコンパイラも、-Oなしですと、20秒以上待たされる程激遅になるのは、確かなようです。
# ↓だけは、さすがに、0.0秒です。
/********* gcc4.8 -O3 (やりすぎて処理が消えている)
.file"test.c"
.section.text.startup,"ax",@progbits
.p2align 4,,15
.globlmain
.typemain, @function
main:
.LFB0:
.cfi_startproc
rep; ret
.cfi_endproc
.LFE0:
.sizemain, .-main
.ident"GCC: (FreeBSD Ports Collection) 4.8.0 20120930 (experimental)"
.section.note.GNU-stack,"",@progbits
/********* gcc4.8 -O3 fputcを追加
.file"test.c"
.section.text.startup,"ax",@progbits
.p2align 4,,15
.globlmain
.typemain, @function
main:
.LFB0:
.cfi_startproc
pushq%r13
.cfi_def_cfa_offset 16
.cfi_offset 13, -16
movl$100000, %edi
movl$100000, %r13d
pushq%r12
.cfi_def_cfa_offset 24
.cfi_offset 12, -24
pushq%rbp
.cfi_def_cfa_offset 32
.cfi_offset 6, -32
pushq%rbx
.cfi_def_cfa_offset 40
.cfi_offset 3, -40
subq$8, %rsp
.cfi_def_cfa_offset 48
callmalloc
movl$100000, %edi
movq%rax, %rbp
callmalloc
movq%rax, %r12
.p2align 4,,10
.L2:
xorl%ebx, %ebx
.p2align 4,,10
.L5:
movl$97, %edi
callfputc
movzbl(%r12,%rbx), %edx
movb%dl, 0(%rbp,%rbx)
addq$1, %rbx
cmpq$100000, %rbx
jne.L5
subl$1, %r13d
jne.L2
addq$8, %rsp
.cfi_def_cfa_offset 40
popq%rbx
.cfi_def_cfa_offset 32
popq%rbp
.cfi_def_cfa_offset 24
popq%r12
.cfi_def_cfa_offset 16
popq%r13
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE0:
.sizemain, .-main
.ident"GCC: (FreeBSD Ports Collection) 4.8.0 20120930 (experimental)"
.section.note.GNU-stack,"",@progbits
No.2
- 回答日時:
>例えでいえば、同じことをする数文字のperlの正規表現処理の数100倍の時間がかかっていました。
正規表現そのものには文字列コピーのような機能はないですけど具体的にはどんな処理なんでしょう?
またgccのバージョンも書かれた方がよいかと思います。
FreeBSD 9のgcc 4.7.3だと
void func1(char *d, char *s, int n)
{
for (int i = 0; i < n; i++) {
*d++ = *s++;
}
}
void func2(char *d, char *s, int n)
{
for (int i = 0; i < n; i++) {
}
}
void func3(char *d, char *s)
{
for (;*d++ = *s++;) {
}
}
で出力されるコードはこんな感じです。
.text
.p2align 4,,15
.globl func1
.type func1, @function
func1:
.LFB0:
.cfi_startproc
xorl %eax, %eax
testl %edx, %edx
jle .L1
.p2align 4,,10
.L5:
movzbl (%rsi,%rax), %ecx
movb %cl, (%rdi,%rax)
addq $1, %rax
cmpl %eax, %edx
jg .L5
.L1:
rep
ret
.cfi_endproc
.LFE0:
.size func1, .-func1
.p2align 4,,15
.globl func2
.type func2, @function
func2:
.LFB1:
.cfi_startproc
rep
ret
.cfi_endproc
.LFE1:
.size func2, .-func2
.p2align 4,,15
.globl func3
.type func3, @function
func3:
.LFB2:
.cfi_startproc
.p2align 4,,10
.L10:
movzbl (%rsi), %eax
addq $1, %rsi
movb %al, (%rdi)
addq $1, %rdi
testb %al, %al
jne .L10
rep
ret
.cfi_endproc
.LFE2:
.size func3, .-func3
ほぼ#1の方のおっしゃってる通りですね。
ありがとうございます。
アセンブラリストを出してから、時間とれなくなってしまいました。。汗
FreeBSDのシステムコンパイラ gcc4.2ですと、-O2でも-O3でも
この回答の2倍以上程度のコードになってしまっているようですが、
gcc4.8、4.7.3を試したら、それなりの速度に戻ってくれました。
また、わずかながら、gccよりもllvm-gccのが速い感じもしています。
No.1
- 回答日時:
gccがどのようなオブジェクトを出すのか解りませんが。
>for(i = 0; i < len; i++) {
> /* *dst++=*src++; ここをなくすと、爆速になる */
>}
この後でiを参照していなければ最適化で空のForループなので無くなっている、或いは i =len;に置換されている事が考えられます。
>for(; *dst++ = *src++;);
一旦ポインタをセットすればレジスタ操作のみになるので比較的高速動作すると思われます。
>for(i = 0; i < len; i++) {
>*dst++=*src++;
>}
iのインクリメント、lenとの比較が付加されますからステップ数で数倍になります。
i,Lenをレジスタ上に持たなければ動作速度では数十倍になると思われます。
考えるよりアセンブリリストを出して比較すれば一目瞭然ですね。
お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!
似たような質問が見つかりました
- C言語・C++・C# c言語でユーザ関数を利用して入力された文字列を反転させるプログラムを作りたいです。 3 2023/01/29 19:47
- C言語・C++・C# プログラミングの授業の課題です 1 2023/01/17 22:15
- C言語・C++・C# c言語 プログラムのエラー 1 2023/02/11 20:31
- C言語・C++・C# c言語配列の結合についてです。 なぜうまくいかないのでしょうか。 #include <stdio.h 4 2022/05/30 22:42
- C言語・C++・C# C言語のエラーについて 2 2022/07/11 13:56
- C言語・C++・C# 宣言する関数の形が決まっている状態で、 str1とstr2の文字列をこの順に引っ付けてstrに保存し 2 2022/05/30 18:21
- C言語・C++・C# C++プログラミングコードにポリモーフィズムを取り入れ方を教えてください。 2 2023/06/09 11:17
- C言語・C++・C# 略語の読み方について 2 2023/05/25 12:35
- C言語・C++・C# スタックフレームの消滅 6 2023/05/20 12:33
- C言語・C++・C# const char** p;のとき、free(p)でC4090エラーとなるのはなぜですか 3 2023/03/31 16:28
関連するカテゴリからQ&Aを探す
デイリーランキングこのカテゴリの人気デイリーQ&Aランキング
-
Python、プログラミングについ...
-
C言語初心者 ポインタについて...
-
バッチファイルで以下のような...
-
c++でテンプレートのコードでわ...
-
C言語 配列と関数の練習問題
-
Windows Formアプリからコンソ...
-
win10で、正確な待ち時間の作り方
-
【C言語】全角文字の配列を、全...
-
C言語初心者です、、、お助けく...
-
C#でログファイルにファイルパ...
-
c言語
-
大量のデータを読み込んで表示...
-
だれがとけるの?
-
mallocについて
-
システムエンジニアの適正について
-
どちのほうがすきですか?
-
VisualStudioで、コードを印刷...
-
c言語
-
c言語でイベントフラグを使った...
-
[C言語]fputsとfprintfの違い
マンスリーランキングこのカテゴリの人気マンスリーQ&Aランキング
-
Linux Cプログラミングを学ぶた...
-
右ビットシフト
-
プログラミングについて。 1つ...
-
応用情報技術者試験の令和元年...
-
へんな現象
-
インクリメント演算子のみを用...
-
C#,vb.netで業務用アプリ開発と...
-
gccを行ってもexeファイルが生...
-
Notepad++の関数リスト表示の変...
-
c言語
-
C言語をコンパイルするとコンピ...
-
mallocについて
-
だれがとけるの?
-
C言語 列挙型(enum型)変数について
-
Windows formアプリで データグ...
-
これなにがちがうんですか??
-
VisualStudio2022でC言語プログ...
-
大量のデータを読み込んで表示...
-
プログラミングについての質問...
-
MACで動く実行ファイルをWindow...
おすすめ情報