Zero-copy và tối ưu data transfer

Nguyễn Thanh Tùng
3 min readDec 8, 2018

--

Nhiều ứng dụng hiện nay có nhiệm vụ transfer data từ nguồn (Disk, Socket…) mà không cần thiết thay đổi nội dung (VD: webpage asset files, FTP, proxy…). Trong một chương trình Java, phổ biến là lớp InputStream nằm trong package java.io. Chúng hoạt động bằng cách chia nhỏ data thành những đoạn buffer nhỏ rồi chuyển qua OutPutStream để ghi xuống đích. Một cách cụ thể hơn, đó là khi các ứng dụng web thường transfer những asset file từ server xuống trình duyệt của người dùng. Với những ứng dụng lớn, công việc này làm chi phí overhead tăng khiến cho hệ thống không hoạt động nhanh như mong muốn. Vậy làm sao để tối ưu chúng?

Trước tiên, chúng ta cùng nhau tìm hiểu cơ chế đọc-ghi data thông thường bằng đoạn code đơn giản sau đây:

Biểu đồ các bước mà nó thực hiện:

Quá trình sẽ trải qua 4 giai đoạn như trên bao gồm:

  • Bước 1: lệnh read() từ chương trình sẽ thực hiện lệnh switch từ user mode sang kernel mode. Sau đó gọi đến DMA(Direct Memory Access) engine để thực hiện bước copy data từ disk đến bộ đệm của kernel
  • Bước 2: data từ bộ đệm của kernel (lấy từ Bước 1) sẽ copy vào bộ đệm của user context, đồng thời thực hiện switch từ kernel mode sang user mode.
  • Bước 3: lệnh write() tiếp tục copy data vừa nhận được từ hàm read() tới bộ đệm của kernel, và context switching lại được thực hiện chuyển sang kernel mode.
  • Bước 4: hàm writer() được return kết quả, từ kernel mode sẽ chuyển đổi lần cuối về lại user mode. Đồng thời đó, data nằm trong bộ đệm của kernel cũng sẽ copy vào DMA engine để chuyển tới nguồn đích.

Như đã thấy, quá trình trên cần đến 4 lần context switching cũng như data copy vào các bộ nhớ đệm. Việc này có thể dẫn tới bottleneckoverhead cost theo đó tăng lên đáng kể.

Phương pháp Zero-copy

Khi nhìn vào các bước thực hiện của phương pháp thông thường phía trên, bạn có thể nhận thấy một số bước copy không thực sự cần thiết. Cụ thể đó là thao tác copy ở bước 2 và 3. Nếu ta có thể thực hiện việc copy từ bộ đệm đọc tới thẳng bộ đệm ghi từ kernel mode thì sẽ tiết kiệm được 2 lần copy cũng như thao tác context switching. Trong Java, phương thức transferTo() nằm trong java.nio.channels.FileChannel thực hiện chính xác điều này.

Đây là đoạn code sử dụng transferTo():

Còn đây là cụ thể các bước chương trình thực hiện:

Như đã thấy, transferTo() chỉ thực hiện gọi tới kernel hệ thống để DMA engine copy từ bộ đệm đọc sang bộ đệm ghi rồi sau cùng chuyển tới nguồn. Có nghĩa là nó đã tiết kiệm được một nửa các bước copy và context switching không cần thiết so với cách thông thường. Trong thực tế, transferTo() (cụ thể hơn là JVM) sẽ thực hiện lệnh gọi hệ thống (syscall) dựa trên cách OS hỗ trợ Zero-copy như sendfile() của Linux hay TransmitFile() của Windows.

Kết luận:

Hiểu về kernel của OS hoạt động giúp ích nhiều cho ta về quá trình performance tuning. Zero-copy cung cấp cách thức hoạt động hiệu quả cho chương trình của chúng ta, đặc biệt là trong những hệ thống lớn. Ngày nay, nhiều webserver đã có hỗ trợ Zero-copy như Nginx, Tomcat, Apache Httpd. Nếu hệ thống của mình cần nhiều đến thao tác transfer data, có lẽ bạn nên cân nhắc sử dụng chúng.

For further details:

--

--