แก้ไขในทุบตี 5.0
พื้นหลัง
สำหรับพื้นหลัง (และความเข้าใจ (และพยายามหลีกเลี่ยง downvotes คำถามนี้ดูเหมือนจะดึงดูด)) ฉันจะอธิบายเส้นทางที่พาฉันไปที่ปัญหานี้ (ดีที่สุดที่ฉันจำได้ในสองเดือนต่อมา)
สมมติว่าคุณทำการทดสอบเชลล์สำหรับรายการอักขระ Unicode:
printf "$(printf '\\U%x ' {33..200})"
และมีอักขระ Unicode มากกว่า 1 ล้านตัวการทดสอบ 20,000 ตัวนั้นดูเหมือนจะไม่มาก
นอกจากนี้สมมติว่าคุณตั้งค่าอักขระเป็นอาร์กิวเมนต์ตำแหน่ง:
set -- $(printf "$(printf '\\U%x ' {33..20000})")
ด้วยความตั้งใจที่จะส่งผ่านตัวละครไปยังแต่ละฟังก์ชั่นเพื่อประมวลผลพวกเขาในรูปแบบที่แตกต่างกัน ดังนั้นฟังก์ชั่นควรมีรูปแบบtest1 "$@"
หรือคล้ายกัน ตอนนี้ฉันรู้แล้วว่าความคิดนี้เลวร้ายแค่ไหน
ทีนี้สมมติว่าจำเป็นต้องใช้เวลา (n = 1,000) แต่ละวิธีแก้ปัญหาเพื่อค้นหาว่าดีกว่าภายใต้เงื่อนไขเช่นนี้คุณจะจบลงด้วยโครงสร้างที่คล้ายกับ:
#!/bin/bash --
TIMEFORMAT='real: %R' # '%R %U %S'
set -- $(printf "$(printf '\\U%x ' {33..20000})")
n=1000
test1(){ echo "$1"; } >/dev/null
test2(){ echo "$#"; } >/dev/null
test3(){ :; }
main1(){ time for i in $(seq $n); do test1 "$@"; done
time for i in $(seq $n); do test2 "$@"; done
time for i in $(seq $n); do test3 "$@"; done
}
main1 "$@"
ฟังก์ชั่นtest#
นั้นทำง่ายมาก ๆ เพียงนำเสนอที่นี่
ต้นฉบับถูกลดระดับลงอย่างช้าๆเพื่อค้นหาว่ามีความล่าช้ามากแค่ไหน
สคริปต์ด้านบนใช้งานได้คุณสามารถเรียกใช้และเสียเวลาไม่กี่วินาทีในการทำน้อยมาก
ในกระบวนการลดความซับซ้อนของการค้นหาว่ามีความล่าช้าตรงไหน (และลดฟังก์ชั่นการทดสอบแต่ละอย่างแทบจะไม่เหลืออะไรเลยหลังจากการทดลองหลายครั้ง) ฉันตัดสินใจที่จะลบการส่งผ่านข้อโต้แย้งไปยังแต่ละฟังก์ชั่นการทดสอบ ปัจจัย 6 ไม่มาก
ในการลองด้วยตัวเองให้ลบ"$@"
ฟังก์ชั่นทั้งหมดmain1
(หรือทำสำเนา) และทดสอบอีกครั้ง (หรือทั้งสองอย่างmain1
และคัดลอกmain2
(พร้อมmain2 "$@"
)) เพื่อเปรียบเทียบ นี่คือโครงสร้างพื้นฐานด้านล่างในโพสต์ต้นฉบับ (OP)
แต่ฉันสงสัยว่าทำไมเปลือกถึงใช้เวลานานในการ "ไม่ทำอะไรเลย" ใช่เพียง "สองสามวินาที" แต่ยังคงทำไม?
นี่ทำให้ฉันทดสอบในกระสุนอื่น ๆ เพื่อค้นพบว่า bash มีปัญหานี้เท่านั้น
ลองksh ./script
(สคริปต์เดียวกันข้างต้น)
สิ่งนี้นำไปสู่คำอธิบายนี้: การเรียกใช้ฟังก์ชั่น ( test#
) โดยไม่มีการโต้แย้งใด ๆ เกิดความล่าช้าโดยอาร์กิวเมนต์ในพาเรนต์ ( main#
) นี่คือคำอธิบายที่ตามมาและเป็นบทความต้นฉบับ (OP) ด้านล่าง
โพสต์ต้นฉบับ
โทรฟังก์ชั่น (ในทุบตี 4.4.12 (1) -release) จะทำอะไรf1(){ :; }
เป็นพันครั้งช้ากว่า:
แต่เฉพาะในกรณีที่มีข้อโต้แย้งที่กำหนดไว้ในปกครองเรียกฟังก์ชั่นทำไม?
#!/bin/bash
TIMEFORMAT='real: %R'
f1 () { :; }
f2 () {
echo " args = $#";
printf '1 function no args yes '; time for ((i=1;i<$n;i++)); do : ; done
printf '2 function yes args yes '; time for ((i=1;i<$n;i++)); do f1 ; done
set --
printf '3 function yes args no '; time for ((i=1;i<$n;i++)); do f1 ; done
echo
}
main1() { set -- $(seq $m)
f2 ""
f2 "$@"
}
n=1000; m=20000; main1
ผลลัพธ์ของtest1
:
args = 1
1 function no args yes real: 0.013
2 function yes args yes real: 0.024
3 function yes args no real: 0.020
args = 20000
1 function no args yes real: 0.010
2 function yes args yes real: 20.326
3 function yes args no real: 0.019
ไม่มีข้อโต้แย้งหรืออินพุตหรือเอาต์พุตที่ใช้ในฟังก์ชันf1
ความล่าช้าของปัจจัยหนึ่งพัน (1,000) เป็นสิ่งที่ไม่คาดคิด 1
การขยายการทดสอบไปยังหลาย ๆ เชลล์ผลลัพธ์นั้นสอดคล้องกันเชลล์ส่วนใหญ่ไม่มีปัญหาหรือเกิดความล่าช้า (ใช้ n และ m เดียวกัน):
test2(){
for sh in dash mksh ksh zsh bash b50sh
do
echo "$sh" >&2
# \time -f '\t%E' seq "$m" >/dev/null
# \time -f '\t%E' "$sh" -c 'set -- $(seq '"$m"'); for i do :; done'
\time -f '\t%E' "$sh" -c 'f(){ :;}; while [ "$((i+=1))" -lt '"$n"' ]; do : ; done;' $(seq $m)
\time -f '\t%E' "$sh" -c 'f(){ :;}; while [ "$((i+=1))" -lt '"$n"' ]; do f ; done;' $(seq $m)
done
}
test2
ผล:
dash
0:00.01
0:00.01
mksh
0:00.01
0:00.02
ksh
0:00.01
0:00.02
zsh
0:00.02
0:00.04
bash
0:10.71
0:30.03
b55sh # --without-bash-malloc
0:00.04
0:17.11
b56sh # RELSTATUS=release
0:00.03
0:15.47
b50sh # Debug enabled (RELSTATUS=alpha)
0:04.62
xxxxxxx More than a day ......
ยกเลิกการคอมเม้นท์อีกสองการทดสอบเพื่อยืนยันว่าไม่มีseq
หรือการประมวลผลรายการอาร์กิวเมนต์เป็นแหล่งที่มาสำหรับความล่าช้า
1มันเป็นที่รู้จักกันว่าผ่านผลการค้นหาตามข้อโต้แย้งที่จะเพิ่มเวลาในการดำเนินการ ขอบคุณ@slm