Bash có vấn đề về hiệu suất khi sử dụng danh sách đối số?


11

Đã giải quyết trong bash 5.0

Lý lịch

Đối với nền tảng (và sự hiểu biết (và cố gắng tránh các câu hỏi hạ thấp câu hỏi này dường như thu hút)) tôi sẽ giải thích con đường đưa tôi đến vấn đề này (tốt nhất, tôi có thể nhớ lại hai tháng sau).

Giả sử bạn đang thực hiện một số kiểm tra trình bao cho danh sách các ký tự Unicode:

printf "$(printf '\\U%x ' {33..200})"

và có hơn 1 triệu ký tự Unicode, thử nghiệm 20.000 trong số chúng dường như không nhiều.
Cũng giả sử rằng bạn đặt các ký tự làm đối số vị trí:

set -- $(printf "$(printf '\\U%x ' {33..20000})")

với ý định chuyển các ký tự cho từng hàm để xử lý chúng theo các cách khác nhau. Vì vậy, các chức năng nên có hình thức test1 "$@"hoặc tương tự. Bây giờ tôi nhận ra ý tưởng tồi tệ như thế nào trong bash.

Bây giờ, giả sử rằng cần có thời gian (một n = 1000) mỗi giải pháp để tìm ra giải pháp nào tốt hơn, trong những điều kiện như vậy, bạn sẽ kết thúc với một cấu trúc tương tự như:

#!/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 "$@"

Các chức năng test#được thực hiện rất rất đơn giản chỉ để được trình bày ở đây.
Các bản gốc đã dần dần được cắt giảm để tìm nơi chậm trễ lớn.

Kịch bản trên hoạt động, bạn có thể chạy nó và lãng phí vài giây làm rất ít.

Trong quá trình đơn giản hóa để tìm chính xác độ trễ ở đâu (và giảm từng hàm kiểm tra xuống gần như không có gì là cực đoan sau nhiều thử nghiệm), tôi quyết định loại bỏ việc truyền các đối số cho từng hàm kiểm tra để tìm hiểu xem thời gian được cải thiện bao nhiêu hệ số 6, không nhiều.

Để tự mình thử, hãy xóa tất cả "$@"hàm trong main1(hoặc tạo một bản sao) và kiểm tra lại (hoặc cả hai main1và bản sao main2(với main2 "$@")) để so sánh. Đây là cấu trúc cơ bản phía dưới trong bài gốc (OP).

Nhưng tôi tự hỏi: tại sao cái vỏ lại mất nhiều thời gian để "không làm gì"?. Vâng, chỉ "một vài giây", nhưng vẫn vậy, tại sao?.

Điều này khiến tôi thử nghiệm trong các shell khác để phát hiện ra rằng chỉ có bash có vấn đề này.
Hãy thử ksh ./script(kịch bản tương tự như trên).

Điều này dẫn đến mô tả này: gọi một hàm ( test#) mà không có bất kỳ đối số nào bị trì hoãn bởi các đối số trong cha ( main#). Đây là mô tả sau đây và là bài viết gốc (OP) bên dưới.

Bài gốc.

Gọi một hàm (trong Bash 4.4.12 (1) -release) để không làm gì f1(){ :; }chậm hơn hàng ngàn lần so với :nhưng chỉ khi có các đối số được xác định trong hàm gọi cha , Tại sao?

#!/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

Kết quả của 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

Không có đối số cũng như đầu vào hoặc đầu ra được sử dụng trong hàm f1, độ trễ của hệ số một nghìn (1000) là không mong muốn. 1


Mở rộng các thử nghiệm đến một số vỏ, kết quả phù hợp, hầu hết các vỏ không gặp khó khăn cũng như không bị chậm trễ (sử dụng cùng n và 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

Các kết quả:

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 ......

Bỏ ghi chú hai bài kiểm tra khác để xác nhận rằng không seqhoặc xử lý danh sách đối số là nguồn cho độ trễ.

1 Đượcbiết rằng việc truyền kết quả bằng các đối số sẽ tăng thời gian thực hiện. Cảm ơn@slm


3
Được lưu bởi hiệu ứng meta. unix.meta.stackexchange.com/q/5021/3562
Joshua

Câu trả lời:


9

Sao chép từ: Tại sao sự chậm trễ trong vòng lặp? theo yêu cầu của bạn:

Bạn có thể rút ngắn trường hợp thử nghiệm thành:

time bash -c 'f(){ :;};for i do f; done' {0..10000}

Nó đang gọi một hàm trong khi $@lớn dường như kích hoạt nó.

Tôi đoán là thời gian được dành để tiết kiệm $@vào một ngăn xếp và khôi phục nó sau đó. Có thể bashnó rất không hiệu quả bằng cách sao chép tất cả các giá trị hoặc một cái gì đó tương tự. Thời gian dường như là trong o (n²).

Bạn nhận được cùng loại thời gian trong các shell khác cho:

time zsh -c 'f(){ :;};for i do f "$@"; done' {0..10000}

Đó là nơi bạn thực hiện chuyển danh sách các đối số cho các hàm và lần này trình bao cần sao chép các giá trị ( bashkết thúc là chậm gấp 5 lần đối với hàm đó).

(Ban đầu tôi nghĩ đó là tồi tệ hơn trong bash 5 (hiện đang trong alpha), nhưng đó là xuống malloc gỡ lỗi được kích hoạt trong các phiên bản phát triển như ghi nhận của @egmont; cũng kiểm tra như thế nào phân phối của bạn được xây dựng bashnếu bạn muốn so sánh xây dựng của riêng bạn với Hệ thống là một. Ví dụ, Ubuntu sử dụng --without-bash-malloc)


Làm thế nào là gỡ lỗi gỡ bỏ?
NotAnUnixNazi

@isaac, tôi đã làm điều đó bằng cách thay đổi RELSTATUS=alphathành RELSTATUS=releasetrong configurekịch bản.
Stéphane Chazelas

Đã thêm kết quả kiểm tra cho cả hai --without-bash-mallocRELSTATUS=releasekết quả câu hỏi. Điều đó vẫn cho thấy một vấn đề với cuộc gọi đến f.
NotAnUnixNazi

@Isaac, vâng, tôi chỉ nói rằng tôi đã từng sai khi nói rằng nó tệ hơn trong bash5. Nó không tệ hơn, nó cũng tệ như vậy.
Stéphane Chazelas

Không, nó không tệ như vậy . Bash5 giải quyết vấn đề với việc gọi :và cải thiện một chút khi gọi f. Nhìn vào thời gian test2 trong câu hỏi.
NotAnUnixNazi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.