Ngay từ lớp học lập trình đầu tiên của tôi ở trường trung học, tôi đã nghe nói rằng các hoạt động chuỗi chậm hơn - tức là tốn kém hơn - so với "hoạt động trung bình" huyền thoại. Tại sao làm cho họ chậm như vậy? (Câu hỏi này để lại có chủ ý rộng.)
Ngay từ lớp học lập trình đầu tiên của tôi ở trường trung học, tôi đã nghe nói rằng các hoạt động chuỗi chậm hơn - tức là tốn kém hơn - so với "hoạt động trung bình" huyền thoại. Tại sao làm cho họ chậm như vậy? (Câu hỏi này để lại có chủ ý rộng.)
Câu trả lời:
"Hoạt động trung bình" diễn ra trên các nguyên thủy. Nhưng ngay cả trong các ngôn ngữ nơi các chuỗi được coi là nguyên thủy, chúng vẫn là các mảng dưới mui xe và làm bất cứ điều gì liên quan đến toàn bộ chuỗi đều mất thời gian O (N), trong đó N là độ dài của chuỗi.
Ví dụ: thêm hai số thường mất 2-4 hướng dẫn ASM. Ghép nối ("thêm") hai chuỗi yêu cầu cấp phát bộ nhớ mới và một hoặc hai bản sao chuỗi, liên quan đến toàn bộ chuỗi.
Một số yếu tố ngôn ngữ có thể làm cho nó tồi tệ hơn. Ví dụ, trong C, một chuỗi chỉ đơn giản là một con trỏ tới một mảng ký tự kết thúc null. Điều này có nghĩa là bạn không biết nó dài bao nhiêu, vì vậy không có cách nào để tối ưu hóa vòng lặp sao chép chuỗi bằng các thao tác di chuyển nhanh; bạn cần sao chép một ký tự một lần để có thể kiểm tra từng byte cho bộ kết thúc null.
char*
, chứ không phải a strbuf
, và bạn quay lại quảng trường 1. Chỉ có rất nhiều bạn có thể làm khi một thiết kế xấu được đưa vào ngôn ngữ.
buf
con trỏ ở đó. Tôi không bao giờ ngụ ý rằng nó không có sẵn; đúng hơn, điều đó là cần thiết. Bất kỳ mã nào không biết về loại chuỗi được tối ưu hóa nhưng không chuẩn của bạn, bao gồm cả những thứ cơ bản như thư viện chuẩn , vẫn phải quay lại chậm, không an toàn char*
. Bạn có thể gọi FUD đó nếu bạn muốn, nhưng điều đó không làm cho nó không đúng.
Đây là một chủ đề cũ và tôi nghĩ rằng các câu trả lời khác là tuyệt vời, nhưng bỏ qua một cái gì đó, vì vậy đây là 2 xu của tôi.
Vấn đề với các chuỗi là chúng là công dân hạng hai trong hầu hết các ngôn ngữ và trên thực tế hầu hết thời gian không thực sự là một phần của đặc tả ngôn ngữ: chúng là một cấu trúc do thư viện thực hiện với một số lớp phủ cú pháp thỉnh thoảng ở trên cùng để làm cho họ bớt đau để sử dụng.
Hậu quả trực tiếp của việc này là ngôn ngữ che giấu một phần rất lớn sự phức tạp của chúng khỏi tầm nhìn của bạn và bạn phải trả giá cho những tác dụng phụ lén lút vì bạn phát triển thành thói quen coi chúng như một thực thể nguyên tử cấp thấp, giống như các loại nguyên thủy khác (như được giải thích bởi câu trả lời được bình chọn hàng đầu và các loại khác).
Một trong những yếu tố của "độ phức tạp" cơ bản này là hầu hết các cài đặt chuỗi sẽ sử dụng cấu trúc dữ liệu đơn giản với một số không gian bộ nhớ liền kề để biểu diễn chuỗi: mảng ol 'tốt của bạn.
Điều này có ý nghĩa tốt, làm phiền bạn, vì bạn muốn truy cập vào toàn bộ chuỗi nhanh chóng. Nhưng điều đó tiềm ẩn chi phí khủng khiếp khi bạn muốn thao tác chuỗi này. Truy cập một phần tử ở giữa có thể nhanh nếu bạn biết bạn đang theo chỉ mục nào, nhưng tìm kiếm một phần tử dựa trên một điều kiện thì không.
Ngay cả việc trả lại kích thước của chuỗi có thể tốn kém, nếu ngôn ngữ của bạn không lưu trữ độ dài của chuỗi và cần phải chạy qua chuỗi đó để đếm các ký tự.
Vì những lý do tương tự, việc thêm các phần tử vào chuỗi của bạn sẽ chứng minh tốn kém vì rất có thể bạn sẽ cần phân bổ lại một số bộ nhớ cho hoạt động này xảy ra.
Vì vậy, các ngôn ngữ khác nhau có cách tiếp cận khác nhau cho các vấn đề này. Chẳng hạn, Java đã tự do biến các chuỗi của nó thành bất biến vì một số lý do hợp lệ (độ dài bộ đệm, an toàn luồng) và cho các đối tác có thể thay đổi của nó (StringBuffer và StringBuilder) sẽ chọn phân bổ kích thước bằng cách sử dụng các khối có kích thước lớn hơn để không cần phân bổ mọi lúc, nhưng hy vọng cho các trường hợp tốt nhất. Nó thường hoạt động tốt, nhưng mặt trái là đôi khi phải trả cho các tác động bộ nhớ.
Ngoài ra, một lần nữa, điều này là do thực tế là lớp phủ đường cú pháp trong ngôn ngữ của bạn che giấu điều này để bạn chơi tốt, bạn thường không nghĩ đó là điều khoản hỗ trợ unicode (đặc biệt là miễn là bạn không thực sự cần nó và đánh vào bức tường đó). Và một số ngôn ngữ, đang suy nghĩ về phía trước, không triển khai các chuỗi với các mảng cơ bản của các nguyên hàm char 8 bit đơn giản. Họ đã nướng trong UTF-8 hoặc UTF-16 hoặc những gì bạn có hỗ trợ cho bạn và hậu quả là mức tiêu thụ bộ nhớ lớn hơn rất nhiều, thường không cần thiết và thời gian xử lý lớn hơn để phân bổ bộ nhớ, xử lý chuỗi, và thực hiện tất cả các logic đi đôi với thao tác các điểm mã.
Kết quả của tất cả những điều này, là khi bạn làm một cái gì đó tương đương bằng mã giả thành:
hello = "hello,"
world = " world!"
str = hello + world
Có thể là không - mặc dù tất cả những nỗ lực tốt nhất mà các nhà phát triển ngôn ngữ đã bỏ ra để họ hành xử như bạn ngoại trừ - một cách đơn giản như:
a = 1;
b = 2;
shouldBeThree = a + b
Theo dõi, bạn có thể muốn đọc:
Cụm từ "hoạt động trung bình" có lẽ là viết tắt cho một hoạt động duy nhất của máy Chương trình lưu trữ truy cập ngẫu nhiên theo lý thuyết . Đây là cỗ máy lý thuyết mà nó được sử dụng để phân tích thời gian chạy của các thuật toán khác nhau.
Các hoạt động chung thường được thực hiện để tải, thêm, trừ, lưu trữ, chi nhánh. Có lẽ cũng đọc, in và tạm dừng.
Nhưng hầu hết các hoạt động chuỗi yêu cầu một số các hoạt động cơ bản. Ví dụ, sao chép một chuỗi thường yêu cầu thao tác sao chép và do đó, một số thao tác tỷ lệ thuận với độ dài của chuỗi (nghĩa là "tuyến tính"). Tìm một chuỗi con bên trong một chuỗi khác cũng có độ phức tạp tuyến tính.
Nó hoàn toàn phụ thuộc vào hoạt động, cách biểu diễn chuỗi và tối ưu hóa tồn tại. Nếu các chuỗi có độ dài 4 hoặc 8 byte (và được căn chỉnh), thì chúng không nhất thiết phải chậm hơn - nhiều thao tác sẽ nhanh như nguyên thủy. Hoặc, nếu tất cả các chuỗi có hàm băm 32 bit hoặc 64 bit, nhiều thao tác cũng sẽ nhanh như vậy (mặc dù bạn phải trả chi phí băm trước).
Nó cũng phụ thuộc vào những gì bạn có nghĩa là "chậm". Hầu hết các chương trình sẽ xử lý chuỗi rất nhanh cho những gì cần thiết. So sánh chuỗi có thể không nhanh bằng so sánh hai int, nhưng chỉ có hồ sơ sẽ tiết lộ "chậm" nghĩa là gì đối với chương trình của bạn.
Hãy để tôi trả lời câu hỏi của bạn bằng một câu hỏi. Tại sao nói một chuỗi từ mất nhiều thời gian hơn nói một từ?