Độ trễ là hành vi ngẫu nhiên của hệ thống khiến công việc có thể nhận biết không chạy được. Trang này mô tả cách xác định và giải quyết các vấn đề về hiện tượng giật liên quan đến độ trễ.
Độ trễ của trình lập lịch biểu luồng ứng dụng
Độ trễ của trình lập lịch biểu là triệu chứng rõ ràng nhất của hiện tượng giật: Một quy trình cần chạy được tạo để có thể chạy nhưng không chạy trong một khoảng thời gian đáng kể. Mức độ quan trọng của độ trễ thay đổi tuỳ theo ngữ cảnh. Ví dụ:
- Một luồng trợ giúp ngẫu nhiên trong ứng dụng có thể bị trì hoãn nhiều mili giây mà không gặp vấn đề gì.
- Luồng giao diện người dùng của ứng dụng có thể chịu được độ trễ 1-2 mili giây.
- Các luồng trình điều khiển chạy dưới dạng SCHED_FIFO có thể gây ra sự cố nếu có thể chạy trong 500us trước khi chạy.
Bạn có thể xác định thời gian có thể chạy trong systrace bằng thanh màu xanh lam đứng trước phân đoạn đang chạy của một luồng. Bạn cũng có thể xác định thời gian có thể chạy bằng khoảng thời gian giữa sự kiện sched_wakeup
cho một luồng và sự kiện sched_switch
báo hiệu bắt đầu thực thi luồng.
Luồng chạy quá lâu
Các luồng giao diện người dùng của ứng dụng có thể chạy quá lâu có thể gây ra sự cố. Các luồng cấp thấp có thời gian chạy lâu thường có nhiều nguyên nhân, nhưng việc cố gắng đẩy thời gian chạy luồng giao diện người dùng về 0 có thể cần khắc phục một số vấn đề tương tự khiến các luồng cấp thấp có thời gian chạy lâu. Để giảm thiểu độ trễ, hãy làm như sau:
- Sử dụng cpuset như mô tả trong phần Điều tiết nhiệt.
- Tăng giá trị CONFIG_HZ.
- Trước đây, giá trị này được đặt thành 100 trên các nền tảng arm và arm64. Tuy nhiên, đây là một sự cố trong quá khứ và không phải là giá trị phù hợp để sử dụng cho các thiết bị tương tác. CONFIG_HZ=100 có nghĩa là một jiffy có thời lượng 10 mili giây, tức là việc cân bằng tải giữa các CPU có thể mất 20 mili giây (2 jiffy). Điều này có thể đóng góp đáng kể vào hiện tượng giật trên hệ thống đã tải.
- Các thiết bị gần đây (Nexus 5X, Nexus 6P, Pixel và Pixel XL) đã được vận chuyển với CONFIG_HZ=300. Điều này sẽ có chi phí điện năng không đáng kể trong khi cải thiện đáng kể thời gian chạy. Nếu bạn thấy mức tiêu thụ điện năng tăng lên đáng kể hoặc gặp vấn đề về hiệu suất sau khi thay đổi CONFIG_HZ, thì có thể một trong các trình điều khiển của bạn đang sử dụng bộ hẹn giờ dựa trên jiffies thô thay vì mili giây và chuyển đổi sang jiffies. Thông thường, bạn có thể dễ dàng khắc phục vấn đề này (xem bản vá đã khắc phục các vấn đề về bộ hẹn giờ kgsl trên Nexus 5X và 6P khi chuyển đổi sang CONFIG_HZ=300).
- Cuối cùng, chúng tôi đã thử nghiệm CONFIG_HZ=1000 trên Nexus/Pixel và nhận thấy giá trị này giúp giảm đáng kể hiệu suất và mức tiêu thụ điện năng do giảm mức hao tổn RCU.
Chỉ với hai thay đổi đó, thiết bị sẽ trông tốt hơn nhiều về thời gian có thể chạy luồng giao diện người dùng khi tải.
Sử dụng sys.use_fifo_ui
Bạn có thể cố gắng đưa thời gian chạy luồng giao diện người dùng về 0 bằng cách đặt thuộc tính sys.use_fifo_ui
thành 1.
Cảnh báo: Không sử dụng tuỳ chọn này trên các cấu hình CPU không đồng nhất, trừ phi bạn có trình lập lịch biểu RT nhận biết được dung lượng.
Và tại thời điểm này, KHÔNG CÓ Trình lập lịch biểu RT ĐANG CHUYỂN PHÁT nào KHÔNG CÓ KHẢ NĂNG NHẬN THỨC VỀ KHẢ NĂNG. Chúng tôi đang nỗ lực để tạo một ứng dụng tương tự cho EAS, nhưng ứng dụng này chưa có sẵn. Trình lập lịch biểu RT mặc định chỉ dựa trên mức độ ưu tiên RT và liệu CPU có luồng RT có mức độ ưu tiên bằng hoặc cao hơn hay không.
Do đó, trình lập lịch biểu RT mặc định sẽ dễ dàng di chuyển luồng giao diện người dùng chạy tương đối lâu từ lõi lớn tần suất cao sang lõi nhỏ ở tần suất tối thiểu nếu một luồng kFIFO có mức độ ưu tiên cao hơn tình cờ thức dậy trên cùng một lõi lớn. Điều này sẽ gây ra sự hồi quy hiệu suất đáng kể. Vì tuỳ chọn này chưa được sử dụng trên thiết bị Android vận chuyển, nên nếu bạn muốn sử dụng tuỳ chọn này, hãy liên hệ với nhóm hiệu suất Android để được xác thực.
Khi sys.use_fifo_ui
được bật, ActivityManager sẽ theo dõi luồng giao diện người dùng và RenderThread (hai luồng quan trọng nhất đối với giao diện người dùng) của ứng dụng hàng đầu và đặt các luồng đó thành SCHED_FIFO thay vì SCHED_OTHER. Điều này giúp loại bỏ hiệu quả hiện tượng giật từ giao diện người dùng và RenderThreads; các dấu vết mà chúng tôi đã thu thập khi bật tuỳ chọn này cho thấy thời gian có thể chạy theo thứ tự micro giây thay vì mili giây.
Tuy nhiên, do bộ cân bằng tải RT không nhận biết được dung lượng, hiệu suất khởi động ứng dụng đã giảm 30% vì luồng giao diện người dùng chịu trách nhiệm khởi động ứng dụng sẽ được chuyển từ lõi Kryo vàng 2,1 GHz sang lõi Kryo bạc 1,5 GHz. Với bộ cân bằng tải RT có khả năng nhận biết dung lượng, chúng tôi nhận thấy hiệu suất tương đương trong các hoạt động hàng loạt và giảm 10-15% thời gian khung hình ở phân vị thứ 95 và 99 trong nhiều điểm chuẩn giao diện người dùng.
Gián đoạn lưu lượng truy cập
Vì theo mặc định, các nền tảng ARM chỉ phân phối các ngắt cho CPU 0, nên bạn nên sử dụng bộ cân bằng IRQ (irqbalance hoặc msm_irqbalance trên các nền tảng Qualcomm).
Trong quá trình phát triển Pixel, chúng tôi nhận thấy hiện tượng giật có thể trực tiếp do CPU 0 bị quá tải với các ngắt. Ví dụ: nếu luồng mdss_fb0
được lên lịch trên CPU 0, thì khả năng bị giật sẽ cao hơn nhiều do một sự kiện ngắt được màn hình kích hoạt gần như ngay lập tức trước khi quét. mdss_fb0
sẽ đang thực hiện công việc của riêng mình với thời hạn rất chặt chẽ, sau đó sẽ mất một chút thời gian để xử lý ngắt MDSS. Ban đầu, chúng tôi đã cố gắng khắc phục vấn đề này bằng cách đặt đối tượng tương đồng CPU của luồng mdss_fb0 thành CPU 1-3 để tránh xung đột với sự kiện ngắt, nhưng sau đó chúng tôi nhận ra rằng mình chưa bật msm_irqbalance. Khi bật msm_irqbalance, hiện tượng giật đã được cải thiện đáng kể ngay cả khi cả mdss_fb0 và ngắt MDSS đều nằm trên cùng một CPU do giảm tình trạng tranh chấp từ các ngắt khác.
Bạn có thể xác định điều này trong systrace bằng cách xem phần sched cũng như phần irq. Mục sched cho biết những gì đã được lên lịch, nhưng một vùng chồng chéo trong mục irq có nghĩa là một ngắt đang chạy trong thời gian đó thay vì quy trình được lên lịch thông thường. Nếu bạn thấy các khoảng thời gian đáng kể trong quá trình gián đoạn, bạn có thể làm như sau:
- Tăng tốc trình xử lý ngắt.
- Trước tiên, hãy ngăn chặn việc gián đoạn xảy ra.
- Thay đổi tần suất của tín hiệu ngắt để không đồng bộ với các tác vụ thông thường khác mà tín hiệu ngắt có thể gây nhiễu (nếu đó là tín hiệu ngắt thông thường).
- Đặt trực tiếp đối tượng tương đồng CPU của ngắt và ngăn đối tượng tương đồng đó cân bằng.
- Đặt đối tượng tương đồng CPU của luồng bị gián đoạn để tránh gián đoạn.
- Dựa vào trình cân bằng ngắt để chuyển ngắt sang một CPU tải ít hơn.
Bạn thường không nên đặt CPU Affinity nhưng có thể hữu ích trong một số trường hợp cụ thể. Nhìn chung, rất khó để dự đoán trạng thái của hệ thống đối với hầu hết các ngoại lệ phổ biến, nhưng nếu bạn có một tập hợp các điều kiện rất cụ thể kích hoạt một số ngoại lệ nhất định trong đó hệ thống bị ràng buộc nhiều hơn bình thường (chẳng hạn như VR), thì đối tượng tương đồng CPU rõ ràng có thể là một giải pháp hiệu quả.
Softirq dài
Khi đang chạy, softirq sẽ tắt tính năng ưu tiên. softirq cũng có thể được kích hoạt ở nhiều vị trí trong nhân và có thể chạy bên trong một quy trình của người dùng. Nếu có đủ hoạt động softirq, các quy trình của người dùng sẽ ngừng chạy softirq và ksoftirqd sẽ thức để chạy softirq và được cân bằng tải. Thông thường, điều này không gây ra vấn đề gì. Tuy nhiên, một softirq rất dài có thể gây ra sự tàn phá cho hệ thống.
softirqs hiển thị trong phần irq của dấu vết, vì vậy, bạn có thể dễ dàng phát hiện nếu có thể tái tạo vấn đề trong khi theo dõi. Vì softirq có thể chạy trong một quy trình của người dùng, nên softirq xấu cũng có thể biểu thị dưới dạng thời gian chạy bổ sung bên trong quy trình của người dùng mà không có lý do rõ ràng. Nếu bạn thấy điều đó, hãy kiểm tra phần irq để xem liệu softirq có phải là nguyên nhân hay không.
Trình điều khiển để chế độ ưu tiên hoặc IRQ bị tắt quá lâu
Việc tắt tính năng ưu tiên hoặc gián đoạn quá lâu (hàng chục mili giây) sẽ dẫn đến hiện tượng giật. Thông thường, hiện tượng giật xuất hiện khi một luồng có thể chạy nhưng không chạy trên một CPU cụ thể, ngay cả khi luồng có thể chạy có mức độ ưu tiên cao hơn đáng kể (hoặc SCHED_FIFO) so với luồng khác.
Một số nguyên tắc:
- Nếu luồng có thể chạy là SCHED_FIFO và luồng đang chạy là SCHED_OTHER, thì luồng đang chạy sẽ bị tắt tính năng ưu tiên hoặc gián đoạn.
- Nếu luồng có thể chạy có mức độ ưu tiên cao hơn đáng kể (100) so với luồng đang chạy (120), thì luồng đang chạy có thể bị vô hiệu hoá tính năng can thiệp trước hoặc ngắt nếu luồng có thể chạy không chạy trong vòng hai jiffies.
- Nếu luồng có thể chạy và luồng đang chạy có cùng mức độ ưu tiên, thì luồng đang chạy có thể bị tắt tính năng can thiệp trước hoặc bị gián đoạn nếu luồng có thể chạy không chạy trong vòng 20 mili giây.
Xin lưu ý rằng việc chạy trình xử lý ngắt sẽ ngăn bạn phục vụ các ngắt khác, đồng thời vô hiệu hoá tính năng can thiệp trước.
Một cách khác để xác định các vùng vi phạm là sử dụng trình theo dõi preemptirqsoff (xem phần Sử dụng ftrace động). Trình theo dõi này có thể cung cấp thông tin chi tiết hơn nhiều về nguyên nhân gốc rễ của một vùng không thể ngắt (chẳng hạn như tên hàm), nhưng cần nhiều thao tác xâm nhập hơn để bật. Mặc dù có thể ảnh hưởng nhiều hơn đến hiệu suất, nhưng bạn vẫn nên thử.
Sử dụng không chính xác hàng đợi công việc
Trình xử lý ngắt thường cần thực hiện công việc có thể chạy bên ngoài ngữ cảnh ngắt, cho phép công việc được phân bổ cho nhiều luồng trong hạt nhân. Nhà phát triển trình điều khiển có thể nhận thấy hạt nhân có chức năng tác vụ không đồng bộ trên toàn hệ thống rất thuận tiện có tên là workqueues và có thể sử dụng chức năng đó cho công việc liên quan đến ngắt.
Tuy nhiên, hàng đợi công việc hầu như luôn là câu trả lời sai cho vấn đề này vì chúng luôn là SCHED_OTHER. Nhiều ngắt phần cứng nằm trong đường dẫn quan trọng của hiệu suất và phải được chạy ngay lập tức. Hàng đợi công việc không đảm bảo thời điểm sẽ chạy. Mỗi khi chúng ta thấy một workqueue trong đường dẫn quan trọng của hiệu suất, đó là nguồn gây ra hiện tượng giật không thường xuyên, bất kể thiết bị. Trên Pixel, với một bộ xử lý hàng đầu, chúng tôi nhận thấy một hàng đợi công việc có thể bị trễ tối đa 7 mili giây nếu thiết bị đang chịu tải, tuỳ thuộc vào hành vi của trình lập lịch biểu và các hoạt động khác đang chạy trên hệ thống.
Thay vì workqueue, các trình điều khiển cần xử lý công việc giống như ngắt bên trong một luồng riêng biệt nên tạo kthread SCHED_FIFO của riêng mình. Để được trợ giúp làm việc này với các hàm kthread_work, hãy tham khảo bản vá này.
Tranh chấp khoá khung
Xung đột khoá khung có thể là nguồn gốc gây ra hiện tượng giật hoặc các vấn đề về hiệu suất khác. Lỗi này thường do khoá ActivityManagerService gây ra, nhưng cũng có thể xuất hiện trong các khoá khác. Ví dụ: khoá PowerManagerService có thể ảnh hưởng đến hiệu suất của màn hình. Nếu bạn thấy lỗi này trên thiết bị của mình, thì không có cách khắc phục nào hiệu quả vì lỗi này chỉ có thể được cải thiện thông qua các điểm cải tiến về cấu trúc cho khung. Tuy nhiên, nếu bạn đang sửa đổi mã chạy bên trong system_server, thì điều quan trọng là bạn phải tránh giữ khoá trong thời gian dài, đặc biệt là khoá ActivityManagerService.
Tranh chấp khoá liên kết
Trước đây, liên kết có một khoá toàn cục duy nhất. Nếu luồng đang chạy giao dịch liên kết bị chiếm quyền khi giữ khoá, thì không có luồng nào khác có thể thực hiện giao dịch liên kết cho đến khi luồng ban đầu giải phóng khoá. Điều này là không tốt; tình trạng tranh chấp liên kết có thể chặn mọi thứ trong hệ thống, bao gồm cả việc gửi nội dung cập nhật giao diện người dùng đến màn hình (luồng giao diện người dùng giao tiếp với SurfaceFlinger thông qua liên kết).
Android 6.0 bao gồm một số bản vá để cải thiện hành vi này bằng cách tắt tính năng chiếm quyền trước khi giữ khoá liên kết. Điều này chỉ an toàn vì khoá liên kết sẽ được giữ trong vài micro giây thời gian chạy thực tế. Điều này giúp cải thiện đáng kể hiệu suất trong các tình huống không có tranh chấp và ngăn chặn tranh chấp bằng cách ngăn chặn hầu hết các nút chuyển trình lập lịch biểu trong khi khoá liên kết được giữ. Tuy nhiên, bạn không thể tắt tính năng ưu tiên trong toàn bộ thời gian chạy của việc giữ khoá liên kết, nghĩa là tính năng ưu tiên được bật cho các hàm có thể ngủ (chẳng hạn như copy_from_user), điều này có thể gây ra tình trạng ưu tiên tương tự như trường hợp ban đầu. Khi chúng tôi gửi các bản vá lên trên, họ nhanh chóng cho chúng tôi biết rằng đây là ý tưởng tồi tệ nhất trong lịch sử. (Chúng tôi đồng ý với họ, nhưng cũng không thể tranh cãi về hiệu quả của các bản vá trong việc ngăn chặn hiện tượng giật.)
Xung đột fd trong một quy trình
Điều này hiếm khi xảy ra. Đây có thể không phải là nguyên nhân gây ra hiện tượng giật.
Tuy nhiên, nếu bạn có nhiều luồng trong một quy trình ghi cùng một fd, thì có thể bạn sẽ thấy sự tranh chấp trên fd này. Tuy nhiên, thời điểm duy nhất chúng tôi thấy điều này trong quá trình khởi động Pixel là trong một thử nghiệm mà các luồng có mức độ ưu tiên thấp cố gắng chiếm dụng tất cả thời gian CPU trong khi một luồng có mức độ ưu tiên cao đang chạy trong cùng một quy trình. Tất cả luồng đều ghi vào fd của điểm đánh dấu theo dõi và luồng có mức độ ưu tiên cao có thể bị chặn trên fd của điểm đánh dấu theo dõi nếu một luồng có mức độ ưu tiên thấp đang giữ khoá fd và sau đó bị chiếm quyền. Khi tính năng theo dõi bị tắt khỏi các luồng có mức độ ưu tiên thấp, không có vấn đề về hiệu suất nào xảy ra.
Chúng tôi không thể tái hiện vấn đề này trong bất kỳ trường hợp nào khác, nhưng điều đáng chú ý là đây có thể là nguyên nhân gây ra các vấn đề về hiệu suất trong khi theo dõi.
Các quá trình chuyển đổi không cần thiết khi CPU ở trạng thái rảnh
Khi xử lý IPC, đặc biệt là quy trình đa quy trình, bạn thường thấy các biến thể về hành vi thời gian chạy sau:
- Luồng A chạy trên CPU 1.
- Luồng A đánh thức luồng B.
- Luồng B bắt đầu chạy trên CPU 2.
- Luồng A ngay lập tức chuyển sang trạng thái ngủ để được luồng B đánh thức khi luồng B hoàn tất công việc hiện tại.
Một nguồn hao tổn phổ biến là giữa bước 2 và 3. Nếu CPU 2 ở trạng thái rảnh, bạn phải đưa CPU này về trạng thái hoạt động trước khi luồng B có thể chạy. Tuỳ thuộc vào SOC và mức độ rảnh, thời gian này có thể là hàng chục micro giây trước khi luồng B bắt đầu chạy. Nếu thời gian chạy thực tế của mỗi bên IPC gần bằng với mức hao tổn, thì hiệu suất tổng thể của quy trình đó có thể giảm đáng kể do các quá trình chuyển đổi CPU ở trạng thái rảnh. Trường hợp phổ biến nhất khiến Android gặp phải vấn đề này là xung quanh các giao dịch liên kết và nhiều dịch vụ sử dụng liên kết cuối cùng cũng gặp phải tình huống như mô tả ở trên.
Trước tiên, hãy sử dụng hàm wake_up_interruptible_sync()
trong trình điều khiển hạt nhân và hỗ trợ hàm này từ bất kỳ trình lập lịch biểu tuỳ chỉnh nào. Hãy coi đây là yêu cầu chứ không phải gợi ý. Binder hiện sử dụng tính năng này và nó giúp ích rất nhiều cho các giao dịch liên kết đồng bộ, tránh các quá trình chuyển đổi không cần thiết của CPU ở trạng thái rảnh.
Thứ hai, hãy đảm bảo thời gian chuyển đổi cpuidle của bạn là thực tế và trình quản lý cpuidle đang tính đến các thời gian này một cách chính xác. Nếu SOC của bạn đang chuyển đổi giữa trạng thái rảnh sâu nhất và trạng thái không rảnh, thì bạn sẽ không tiết kiệm được pin bằng cách chuyển sang trạng thái rảnh sâu nhất.
Ghi nhật ký
Việc ghi nhật ký không miễn phí cho chu kỳ CPU hoặc bộ nhớ, vì vậy, đừng làm đầy vùng đệm nhật ký. Chu kỳ ghi lại chi phí trong ứng dụng (trực tiếp) và trong trình nền nhật ký. Xoá mọi nhật ký gỡ lỗi trước khi vận chuyển thiết bị.
Vấn đề về I/O
Các thao tác I/O là nguồn thường gây ra hiện tượng giật. Nếu một luồng truy cập vào một tệp được ánh xạ bộ nhớ và trang không có trong bộ nhớ đệm trang, thì luồng đó sẽ gặp lỗi và đọc trang từ ổ đĩa. Điều này sẽ chặn luồng (thường là hơn 10 mili giây) và nếu xảy ra trong đường dẫn quan trọng của quá trình kết xuất giao diện người dùng, thì có thể dẫn đến hiện tượng giật. Có quá nhiều nguyên nhân gây ra hoạt động I/O để thảo luận ở đây, nhưng hãy kiểm tra các vị trí sau khi cố gắng cải thiện hành vi I/O:
- PinnerService. Được thêm vào Android 7.0, PinnerService cho phép khung này khoá một số tệp trong bộ nhớ đệm trang. Thao tác này sẽ xoá bộ nhớ để bất kỳ quy trình nào khác sử dụng, nhưng nếu có một số tệp được biết trước là thường xuyên được sử dụng, thì việc mlock các tệp đó có thể hiệu quả.
Trên các thiết bị Pixel và Nexus 6P chạy Android 7.0, chúng tôi đã khoá 4 tệp:- /system/framework/arm64/boot-framework.oat
- /system/framework/oat/arm64/services.odex
- /system/framework/arm64/boot.oat
- /system/framework/arm64/boot-core-libart.oat
- Mã hoá. Một nguyên nhân khác có thể gây ra sự cố I/O. Chúng tôi nhận thấy rằng tính năng mã hoá nội tuyến mang lại hiệu suất tốt nhất so với tính năng mã hoá dựa trên CPU hoặc sử dụng khối phần cứng có thể truy cập được qua DMA. Quan trọng nhất, tính năng mã hoá nội tuyến làm giảm độ trễ liên quan đến I/O, đặc biệt là khi so sánh với tính năng mã hoá dựa trên CPU. Vì các lệnh tìm nạp vào bộ nhớ đệm trang thường nằm trong đường dẫn quan trọng của quá trình kết xuất giao diện người dùng, nên việc mã hoá dựa trên CPU sẽ làm tăng thêm tải CPU trong đường dẫn quan trọng, từ đó làm tăng độ trễ nhiều hơn so với việc tìm nạp I/O.
Các công cụ mã hoá phần cứng dựa trên DMA cũng gặp vấn đề tương tự, vì hạt nhân phải dành các chu kỳ để quản lý công việc đó ngay cả khi có thể chạy các công việc quan trọng khác. Bất kỳ nhà cung cấp SOC nào xây dựng phần cứng mới đều nên hỗ trợ tính năng mã hoá nội tuyến.
Gói tác vụ nhỏ một cách linh hoạt
Một số trình lập lịch biểu hỗ trợ đóng gói các tác vụ nhỏ vào một lõi CPU để cố gắng giảm mức tiêu thụ điện năng bằng cách giữ cho nhiều CPU ở trạng thái rảnh trong thời gian dài hơn. Mặc dù phương thức này hoạt động tốt đối với thông lượng và mức tiêu thụ điện năng, nhưng có thể gây ra thảm họa đối với độ trễ. Có một số luồng chạy ngắn trong đường dẫn quan trọng của quá trình kết xuất giao diện người dùng có thể được coi là nhỏ; nếu các luồng này bị trì hoãn khi chúng di chuyển chậm sang các CPU khác, thì điều này sẽ gây ra hiện tượng giật. Bạn nên sử dụng tính năng đóng gói tác vụ nhỏ một cách rất thận trọng.
Bộ nhớ đệm trang bị hao tổn
Một thiết bị không có đủ bộ nhớ trống có thể đột nhiên trở nên cực kỳ chậm khi thực hiện một thao tác chạy trong thời gian dài, chẳng hạn như mở một ứng dụng mới. Dấu vết của ứng dụng có thể cho thấy ứng dụng đó liên tục bị chặn trong I/O trong một lần chạy cụ thể, ngay cả khi thường không bị chặn trong I/O. Đây thường là dấu hiệu của việc bộ nhớ đệm trang bị hao tổn, đặc biệt là trên các thiết bị có ít bộ nhớ.
Một cách để xác định điều này là thực hiện một systrace bằng cách sử dụng thẻ pagecache và nguồn cấp dữ liệu theo dõi đến tập lệnh tại system/extras/pagecache/pagecache.py
. pagecache.py dịch các yêu cầu riêng lẻ để liên kết các tệp vào bộ nhớ đệm trang thành số liệu thống kê tổng hợp trên mỗi tệp. Nếu bạn thấy rằng số byte của một tệp đã được đọc nhiều hơn tổng kích thước của tệp đó trên ổ đĩa, thì chắc chắn bạn đang gặp phải tình trạng bộ nhớ đệm trang bị hao tổn.
Điều này có nghĩa là bộ nhớ hoạt động mà khối lượng công việc của bạn yêu cầu (thường là một ứng dụng cộng với system_server) lớn hơn dung lượng bộ nhớ có sẵn cho bộ nhớ đệm trang trên thiết bị. Do đó, khi một phần của khối lượng công việc nhận được dữ liệu cần thiết trong bộ nhớ đệm trang, một phần khác sẽ được sử dụng trong tương lai gần sẽ bị loại bỏ và phải được tìm nạp lại, khiến vấn đề này tiếp tục xảy ra cho đến khi tải xong. Đây là nguyên nhân cơ bản gây ra các vấn đề về hiệu suất khi không có đủ bộ nhớ trên thiết bị.
Không có cách nào hoàn hảo để khắc phục tình trạng bộ nhớ đệm trang bị hao tổn, nhưng có một số cách để cố gắng cải thiện vấn đề này trên một thiết bị cụ thể.
- Sử dụng ít bộ nhớ hơn trong các quy trình liên tục. Càng ít bộ nhớ được các quy trình liên tục sử dụng, thì càng có nhiều bộ nhớ cho các ứng dụng và bộ nhớ đệm trang.
- Kiểm tra các vùng riêng biệt mà bạn có cho thiết bị để đảm bảo bạn không xoá bộ nhớ khỏi hệ điều hành một cách không cần thiết. Chúng tôi từng ghi nhận nhiều trường hợp các phần được tách riêng dùng để gỡ lỗi vô tình bị bỏ lại trong cấu hình hạt nhân vận chuyển, tiêu tốn hàng chục megabyte bộ nhớ. Điều này có thể tạo ra sự khác biệt giữa việc bộ nhớ đệm trang bị hao tổn và không bị hao tổn, đặc biệt là trên các thiết bị có ít bộ nhớ.
- Nếu bạn thấy bộ nhớ đệm trang bị hao tổn trong system_server trên các tệp quan trọng, hãy cân nhắc ghim các tệp đó. Điều này sẽ làm tăng áp lực bộ nhớ ở nơi khác, nhưng có thể sửa đổi hành vi đủ để tránh tình trạng đơ.
- Điều chỉnh lại lowmemorykiller để cố gắng giải phóng nhiều bộ nhớ hơn. Ngưỡng của lowmemorykiller dựa trên cả bộ nhớ trống tuyệt đối và bộ nhớ đệm trang, vì vậy, việc tăng ngưỡng mà tại đó các quy trình ở một cấp oom_adj nhất định bị loại bỏ có thể dẫn đến hành vi tốt hơn nhưng lại làm tăng số lần ứng dụng bị tắt ở chế độ nền.
- Thử sử dụng ZRAM. Chúng tôi sử dụng ZRAM trên Pixel, mặc dù Pixel có 4GB, vì tính năng này có thể giúp xử lý các trang bẩn hiếm khi được sử dụng.