Kịch bản Bash; tối ưu hóa tốc độ xử lý


10

Tôi đã tự hỏi nếu có hướng dẫn chung để tối ưu hóa các tập lệnh Bash.

  • Ví dụ, việc viết các vòng lặp thuận tiện hơn các dòng lệnh, nhưng nó cũng nhanh hơn để xử lý cho hệ thống? Thí dụ:

    for i in a b c; do echo $i; done
    
    echo a
    echo b
    echo c
  • Đôi khi mọi người trình bày các giải pháp khác nhau cho cùng một vấn đề. Ví dụ, sed, cut, awk, và echotất cả đều có thể dải chữ số từ một chuỗi. Tôi đã tự hỏi nếu bạn có thể nói rằng mã càng ít chữ số thì càng nhanh nếu bạn sử dụng:

    1. cùng một lệnh, vd

      STRING=abc.def
      echo ${STRING} | sed 's/.def//g'
      echo ${STRING} | sed '$s/....$//'
    2. các lệnh khác nhau, vd

      STRING=abc.def
      echo ${STRING} | cut -d . -f 1
      echo ${STRING} | sed 's/.def//g'

2
Trong hầu hết các trường hợp, tôi thích đọc hơn tốc độ.
Bernhard

1
a) Có, có một chi phí sử dụng vòng lặp for nhưng không thành vấn đề b) cố gắng giảm thiểu các lệnh bên ngoài và chỉ thay thế mọi thứ từ bên trong bash. Và nếu bạn phải tối ưu hóa tập lệnh shell của mình, bạn đang làm gì đó sai và nên xem xét sử dụng một ngôn ngữ có hỗ trợ trình lược tả tốt hơn.
Ulrich Dangel

Các biểu thức thông thường thường có hiệu suất kém hiệu quả hơn bất kỳ lựa chọn nào khác, vì vậy nếu bạn thấy một cách để làm một cái gì đó mà không cần một (ví dụ: cắt), hãy sử dụng nó. Tất nhiên, điều đó có thể ảnh hưởng đến khả năng đọc nếu khả năng đọc của bạn bị giới hạn ở các biểu thức thông thường;)
goldilocks

Câu trả lời:


8

Shell không thực hiện bất kỳ việc sắp xếp lại mã nào mà chúng được trao, nó chỉ được diễn giải hết dòng này đến dòng khác (không có ý nghĩa gì khác trong trình thông dịch lệnh). Phần lớn thời gian dành cho shell dành cho phân tích từ vựng / phân tích cú pháp / khởi chạy các chương trình được gọi.

Đối với các thao tác đơn giản (như các chuỗi munging trong các ví dụ ở cuối câu hỏi) Tôi sẽ ngạc nhiên nếu thời gian để tải các chương trình không làm thay đổi bất kỳ sự khác biệt tốc độ cực nhỏ nào.

Đạo đức của câu chuyện là nếu bạn thực sự cần nhiều tốc độ hơn, bạn nên sử dụng một ngôn ngữ được biên dịch (bán) như Perl hoặc Python, để chạy nhanh hơn để bắt đầu, trong đó bạn có thể viết nhiều thao tác được đề cập trực tiếp và không phải gọi ra các chương trình bên ngoài và có tùy chọn gọi các chương trình bên ngoài hoặc gọi vào các mô-đun C (hoặc bất cứ thứ gì) được tối ưu hóa để thực hiện nhiều công việc. Đó là lý do tại sao trong Fedora, "đường quản trị hệ thống" (GUI, về cơ bản) được viết bằng Python: Có thể thêm một GUI đẹp mà không cần quá nhiều nỗ lực, đủ nhanh cho các ứng dụng như vậy, có quyền truy cập trực tiếp vào các cuộc gọi hệ thống. Nếu điều đó không đủ tốc độ, hãy lấy C ++ hoặc C.

Nhưng đừng đến đó, trừ khi bạn có thể chứng minh rằng hiệu suất đạt được là sự mất mát về tính linh hoạt và thời gian phát triển. Các kịch bản Shell không quá tệ để đọc, nhưng tôi rùng mình khi nhớ một số tập lệnh được sử dụng để cài đặt Ultrix mà tôi đã từng cố gắng giải mã. Tôi đã từ bỏ, quá nhiều "tối ưu hóa tập lệnh shell" đã được áp dụng.


1
1 nhưng có rất nhiều người sẽ tranh luận có nhiều khả năng là một lợi ích trong sự linh hoạt và phát triển thời gian sử dụng một cái gì đó như trăn hay perl vs vỏ, không phải là một mất mát. Tôi sẽ nói chỉ sử dụng một tập lệnh shell nếu nó là bắt buộc, hoặc những gì bạn đang làm liên quan đến một số lượng lớn các lệnh cụ thể của shell.
goldilocks

21

Nguyên tắc tối ưu hóa đầu tiên là: không tối ưu hóa . Kiểm tra trước. Nếu các thử nghiệm cho thấy chương trình của bạn quá chậm, hãy tìm kiếm các tối ưu hóa có thể.

Cách duy nhất để chắc chắn là điểm chuẩn cho trường hợp sử dụng của bạn. Có một số quy tắc chung, nhưng chúng chỉ áp dụng cho khối lượng dữ liệu điển hình trong các ứng dụng điển hình.

Một số quy tắc chung có thể đúng hoặc không đúng trong mọi trường hợp cụ thể:

  • Để xử lý nội bộ trong shell, ATT ksh là nhanh nhất. Nếu bạn thực hiện nhiều thao tác chuỗi, hãy sử dụng ATT ksh. Dash đến thứ hai; bash, pdksh và zsh tụt lại phía sau.
  • Nếu bạn cần thường xuyên gọi shell để thực hiện một nhiệm vụ rất ngắn mỗi lần, dash sẽ thắng vì thời gian khởi động thấp.
  • Bắt đầu một quy trình bên ngoài tốn thời gian, do đó, nhanh hơn để có một đường ống với các phần phức tạp hơn một đường ống trong một vòng lặp.
  • echo $foochậm hơn echo "$foo", bởi vì không có dấu ngoặc kép, nó phân tách $foothành các từ và diễn giải mỗi từ dưới dạng mẫu ký tự đại diện. Quan trọng hơn, hành vi chia tách và toàn cầu hiếm khi được mong muốn. Vì vậy, hãy nhớ luôn luôn đặt dấu ngoặc kép quanh thay thế biến và thay thế lệnh: "$foo", "$(foo)".
  • Các công cụ chuyên dụng có xu hướng giành chiến thắng trên các công cụ có mục đích chung. Ví dụ, các công cụ như cuthoặc headcó thể được mô phỏng sed, nhưng sedsẽ chậm hơn và awkthậm chí sẽ chậm hơn. Xử lý chuỗi Shell chậm, nhưng đối với các chuỗi ngắn, nó chủ yếu vượt qua việc gọi một chương trình bên ngoài.
  • Các ngôn ngữ nâng cao hơn như Perl, Python và Ruby thường cho phép bạn viết các thuật toán nhanh hơn, nhưng chúng có thời gian khởi động cao hơn đáng kể vì vậy chúng chỉ đáng để thực hiện cho một lượng lớn dữ liệu.
  • Trên Linux ít nhất, các đường ống có xu hướng nhanh hơn các tệp tạm thời.
  • Hầu hết việc sử dụng kịch bản shell là xung quanh các quy trình ràng buộc I / O, do đó mức tiêu thụ CPU không thành vấn đề.

Thật hiếm khi hiệu suất là một mối quan tâm trong các kịch bản shell. Danh sách trên hoàn toàn là chỉ định; Trong hầu hết các trường hợp, việc sử dụng các phương pháp chậm chậm trong hầu hết các trường hợp là rất tốt vì sự khác biệt thường là một phần trăm.

Thông thường điểm của một kịch bản shell là để hoàn thành một cái gì đó nhanh chóng. Bạn phải đạt được rất nhiều từ việc tối ưu hóa để biện minh cho việc dành thêm phút để viết kịch bản.


2
Trong khi pythonrubychắc chắn là bắt đầu chậm hơn, ít nhất là trên hệ thống của tôi, perllà bắt đầu nhanh như bashhoặc ksh. GNU awk chậm hơn đáng kể so với GNU sed, đặc biệt là ở các địa điểm utf-8, nhưng điều đó không đúng với tất cả các awks và tất cả các sed. ksh93> dash> pdksh> zsh> bash không phải lúc nào cũng rõ ràng như vậy. Một số vỏ tốt hơn ở một số thứ so với những thứ khác và người chiến thắng không phải lúc nào cũng giống nhau.
Stéphane Chazelas

2
Re "bạn phải kiếm được rất nhiều từ ..." : nếu "bạn" bao gồm cơ sở người dùng, đúng. Với các tập lệnh shell trong các gói Linux phổ biến, người dùng thường lãng phí nhiều thời gian hơn so với các lập trình viên vội vàng tiết kiệm.
agc

2

Chúng tôi sẽ mở rộng ở đây trên ví dụ toàn cầu của chúng tôi ở trên để minh họa một số đặc điểm hiệu suất của trình thông dịch kịch bản lệnh shell. So sánh các trình thông dịch bashdashví dụ cho ví dụ này trong đó một quy trình được sinh ra cho mỗi 30.000 tệp, cho thấy dấu gạch ngang có thể rẽ nhánh các wcquy trình nhanh gần gấp đôibash

bash-4.2$ time dash -c 'for i in *; do wc -l "$i"; done>/dev/null'
real    0m1.238s
user    0m0.309s
sys     0m0.815s


bash-4.2$ time bash -c 'for i in *; do wc -l "$i"; done>/dev/null'
real    0m1.422s
user    0m0.349s
sys     0m0.940s

So sánh tốc độ vòng lặp cơ sở bằng cách không gọi các wcquy trình, cho thấy rằng vòng lặp của dấu gạch ngang nhanh hơn gần 6 lần!

$ time bash -c 'for i in *; do echo "$i">/dev/null; done'
real    0m1.715s
user    0m1.459s
sys     0m0.252s



$ time dash -c 'for i in *; do echo "$i">/dev/null; done'
real    0m0.375s
user    0m0.169s
sys     0m0.203s

Việc lặp vẫn tương đối chậm trong cả hai vỏ như đã trình bày trước đây, vì vậy để có khả năng mở rộng, chúng ta nên thử và sử dụng các kỹ thuật chức năng nhiều hơn để việc lặp được thực hiện trong các quy trình được biên dịch.

$ time find -type f -print0 | wc -l --files0-from=- | tail -n1
    30000 total
real    0m0.299s
user    0m0.072s
sys     0m0.221s

Trên đây là giải pháp hiệu quả nhất và minh họa rõ ràng rằng người ta nên làm ít nhất có thể trong tập lệnh shell và chỉ nhằm sử dụng nó để kết nối logic hiện có trong bộ tiện ích phong phú có sẵn trên hệ thống UNIX.

Bị đánh cắp từ những lỗi kịch bản shell thông thường của Pádraig Brady.


1
Một quy tắc chung: xử lý mô tả tập tin cũng chi phí, vì vậy giảm số lượng của họ. Thay vì for i in *; do wc -l "$i">/dev/null; donelàm tốt hơn for i in *; do wc -l "$i"; done>/dev/null.
manatwork

@manatwork nó cũng sẽ null đầu ra của timecmd
Rahul Patil

@manatwork Tốt ... bây giờ Xin vui lòng cho tôi đầu ra mà không cần gọi wc -l, kiểm tra xem tôi đã cập nhật trong bài viết đầu ra của bạn
Rahul Patil

Vâng, các phép đo trước đó được thực hiện trên một thư mục nhỏ hơn. Bây giờ tôi đã tạo một tệp với 30000 tệp và lặp lại các thử nghiệm: pastebin.com/pCV6QKp2
manatwork

Những điểm chuẩn đó không cho phép thời gian bắt đầu khác nhau của mỗi vỏ. Điểm chuẩn được thực hiện từ bên trong mỗi vỏ sẽ tốt hơn.
agc
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.