Ba nút chỉnh hiệu năng lớn nhất của Vert.x là số bản sao Verticle, eventLoopPoolSize, và kích thước pool worker. Lời khuyên phổ biến cho hai nút đầu là "bằng số nhân CPU". Tôi quét cả ba trên một máy 16 nhân.

Hai nút đầu không đạt đỉnh ở 16.

Số bản sao Verticle

Route tốn CPU vừa phải (một vòng lặp 20 000 bước), 200 kết nối, 8 giây:

Số bản sao Thông lượng p50
1 72 388 req/s 2,4 ms
2 110 416 req/s 1,6 ms
4 113 195 req/s 1,6 ms
8 107 851 req/s 1,7 ms
16 102 821 req/s 1,8 ms
32 102 351 req/s 1,8 ms

Từ 1 lên 2 tăng 53%. Từ 2 lên 4 tăng thêm 2,5%. Từ 4 trở đi giảm dần, và ở 16 bản sao — đúng con số "một cho mỗi nhân" — thông lượng thấp hơn đỉnh 9%.

eventLoopPoolSize

Giữ 16 bản sao Verticle, đổi số event loop mà chúng được rải lên:

eventLoopPoolSize Luồng thật Thông lượng
1 1 73 878 req/s
2 2 114 988 req/s
4 4 106 909 req/s
8 8 103 513 req/s
16 16 102 580 req/s

Cùng hình dạng: đỉnh ở 2, rồi tụt đều. Và chú ý là 16 bản sao Verticle chạy trên 2 event loop nhanh hơn 16 bản sao trên 16 event loop — số Verticle không quyết định, số event loop mới quyết định, và nhiều hơn không tốt hơn.

Một điều kiện quan trọng của phép đo này: bộ sinh tải chạy trên cùng máy 16 nhân và nó cũng ngốn nhiều nhân. Nghĩa là máy chủ chưa bao giờ có đủ 16 nhân rảnh, và con số đỉnh 2–4 là của riêng bối cảnh này. Nếu bạn đo với bộ sinh tải ở máy khác, đỉnh sẽ dịch sang phải.

Cái chuyển được sang hoàn cảnh khác không phải con số mà là hình dạng: tăng nhanh lúc đầu, đỉnh sớm hơn số nhân, rồi tụt nhẹ chứ không phẳng. Phần tụt đến từ việc thêm luồng là thêm tranh chấp bộ nhớ đệm và thêm chuyển ngữ cảnh, trong khi công việc thì không tăng.

Nên luật thực dụng là: đừng chọn theo công thức, hãy quét như bảng trên rồi lấy đỉnh — mất chừng mười phút và cho đúng con số của máy bạn, tải của bạn.

Pool worker thì ngược lại: tuyến tính

Route đẩy việc chặn 5 ms sang WorkerExecutor, vẫn 200 kết nối:

Pool Thông lượng p50
4 662 req/s 300,2 ms
10 1 681 req/s 118,4 ms
20 3 372 req/s 59,0 ms
50 8 503 req/s 23,4 ms
100 16 969 req/s 11,7 ms
200 32 144 req/s 6,1 ms
400 31 479 req/s 6,2 ms

Tuyến tính gần như hoàn hảo cho tới 200, rồi phẳng. Lý do đơn giản và đáng nhớ: việc chặn không tiêu CPU, nó chỉ chiếm một luồng đang chờ. Thêm luồng là thêm số việc chờ được song song, cho tới khi số luồng bằng số việc thật sự có mặt cùng lúc — ở đây là 200, đúng bằng số kết nối của bộ sinh tải. Từ đó trở đi, luồng thừa chỉ ngồi không.

Đối chiếu với công thức: mỗi việc đo được là 5,9 ms (không phải 5 ms — Thread.sleep(5) luôn ngủ lâu hơn một chút), nên 200 luồng cho trần 200 / 0,0059 = 33 900 req/s. Đo được 32 144. Khớp.

Vậy hai nút này khác nhau về bản chất:

Đo cái gì Chọn thế nào
Event loop năng lực CPU quét tìm đỉnh, thường nhỏ hơn số nhân
Pool worker số việc chờ song song bằng số việc đồng thời bạn muốn phục vụ

Và nhớ điều phần 31 đã đo: pool worker to là một cái xô chung, nên "bằng số việc đồng thời" phải tính riêng cho từng loại việc bằng các WorkerExecutor khác nhau, không phải một pool khổng lồ cho tất cả.

Phép đo đầu tiên của tôi sai, và lý do rất tầm thường

Lần quét pool worker đầu tiên cho kết quả này:

pool   4 : 3 345 req/s
pool  10 : 3 305 req/s
pool  20 : 3 372 req/s
pool 100 : 3 373 req/s
pool 400 : 3 344 req/s

Phẳng lì. Kết luận sẽ là "kích thước pool không ảnh hưởng gì" — nghe phản trực giác nên hấp dẫn, và hoàn toàn sai.

Điều cứu tôi là một phép kiểm tra số học: pool 4 luồng, mỗi việc 5 ms, thì trần lý thuyết là 800 req/s. Đo được 3 345. Một con số vượt trần vật lý của chính nó là dấu hiệu phép đo hỏng, chứ không phải phát hiện.

Nguyên nhân là một dòng shell:

chay "-Dban=4 -DthoPool=$TP"      # ham nhan MOT chuoi
java -Xmx1g $1 -cp ...            # zsh khong tach tu -> ca chuoi thanh MOT tham so

zsh không tách từ biến khi mở rộng, nên java nhận một tham số -Dban=4 -DthoPool=4 thay vì hai cờ. Tôi truyền cờ riêng lẻ rồi đo lại, và cho thêm một endpoint đếm số việc hoàn thành ở phía máy chủ để đối chiếu:

pool   4 : khach do 671 req/s | may chu dem 697 req/s | tran ly thuyet 673 req/s
pool 100 : khach do 17 064    | may chu dem 17 099    | tran ly thuyet 17 158

Ba con số khớp nhau. Bảng ở mục trên là bảng sau khi sửa.

Đây là lần thứ ba trong sê-ri này tôi dính đúng lỗi zsh đó. Cách phòng đơn giản: luôn có một phép kiểm tra số học độc lập cho mỗi phép đo. Trần lý thuyết, số việc máy chủ tự đếm, hay chỉ cần một câu hỏi "con số này có khả dĩ không" — bất cứ thứ gì không đến từ cùng một đường ống với con số đang nghi ngờ.

Ba câu hỏi trước khi chỉnh

  • Bạn đang nghẽn ở CPU hay ở chờ đợi? Nghẽn CPU thì chỉnh event loop, và đỉnh sẽ đến sớm. Nghẽn chờ đợi thì chỉnh pool worker, và nó tuyến tính.
  • Bộ sinh tải có chạy cùng máy không? Nếu có, mọi con số đỉnh của bạn đều lệch trái.
  • Con số đo được có vượt trần lý thuyết không? Nếu có, sửa phép đo trước khi tin nó.

Thử ba mươi giây

Quét một nút chỉnh, không cần công cụ gì:

for N in 1 2 4 8 16 32; do
  java -Dban=$N -jar app.jar &
  sleep 5
  echo -n "  $N ban sao: "; ./bo-sinh-tai http://localhost:8080/duong-nong 200 8
  pkill -f app.jar
done

Đỉnh của bạn gần như chắc chắn không nằm ở "số nhân CPU". Của tôi nằm ở một phần tư con số đó.

Phần sau là danh sách kiểm trước khi đưa ứng dụng Vert.x lên sản xuất.