Chuyện tối ưu code, xấu đẹp, đẹp xấu
optimize code
1
White

Hoàng Nguyễn viết ngày 29/11/2017

Chuyện tối ưu code, viết code cho thật đẹp là công việc hàng ngày của mỗi lập trình viên, điều đó ai cũng biết. Nhưng liệu code tối ưu có phải là code đẹp, và ngược lại? Đây là câu hỏi mà chắc hẳn nhiều bạn đều thắc mắc. Tôi sẽ kể một câu chuyện, và kết luận thế nào thì các bạn vui lòng kéo xuống phần “Kết luận” để đọc nhé.

Vậy câu chuyện là gì?

Chuyện là một đêm Sài Gòn mát lạnh (do tôi ở trong phòng mở máy lạnh, chứ ngoài trời thì tôi không biết), tôi rảnh rỗi ngồi lướt Github (thay vì lướt Facebook) thì tình cờ tôi đọc được một đoạn code khá là thú vị.

  vec4 tex00 = texture2D(textureSampler, texCoords + vec2(-off2.x, -off2.y));
  vec4 tex10 = texture2D(textureSampler, texCoords + vec2(-off.x, -off2.y));
  vec4 tex20 = texture2D(textureSampler, texCoords + vec2(0.0, -off2.y));
  vec4 tex30 = texture2D(textureSampler, texCoords + vec2(off.x, -off2.y));
  vec4 tex40 = texture2D(textureSampler, texCoords + vec2(off2.x, -off2.y));

  vec4 tex01 = texture2D(textureSampler, texCoords + vec2(-off2.x, -off.y));
  vec4 tex11 = texture2D(textureSampler, texCoords + vec2(-off.x, -off.y));
  vec4 tex21 = texture2D(textureSampler, texCoords + vec2(0.0, -off.y));
  vec4 tex31 = texture2D(textureSampler, texCoords + vec2(off.x, -off.y));
  vec4 tex41 = texture2D(textureSampler, texCoords + vec2(off2.x, -off.y));

  vec4 tex02 = texture2D(textureSampler, texCoords + vec2(-off2.x, 0.0));
  vec4 tex12 = texture2D(textureSampler, texCoords + vec2(-off.x, 0.0));
  vec4 tex22 = texture2D(textureSampler, texCoords + vec2(0.0, 0.0));
  vec4 tex32 = texture2D(textureSampler, texCoords + vec2(off.x, 0.0));
  vec4 tex42 = texture2D(textureSampler, texCoords + vec2(off2.x, 0.0));

  vec4 tex03 = texture2D(textureSampler, texCoords + vec2(-off2.x, off.y));
  vec4 tex13 = texture2D(textureSampler, texCoords + vec2(-off.x, off.y));
  vec4 tex23 = texture2D(textureSampler, texCoords + vec2(0.0, off.y));
  vec4 tex33 = texture2D(textureSampler, texCoords + vec2(off.x, off.y));
  vec4 tex43 = texture2D(textureSampler, texCoords + vec2(off2.x, off.y));

  vec4 tex04 = texture2D(textureSampler, texCoords + vec2(-off2.x, off2.y));
  vec4 tex14 = texture2D(textureSampler, texCoords + vec2(-off.x, off2.y));
  vec4 tex24 = texture2D(textureSampler, texCoords + vec2(0.0, off2.y));
  vec4 tex34 = texture2D(textureSampler, texCoords + vec2(off.x, off2.y));
  vec4 tex44 = texture2D(textureSampler, texCoords + vec2(off2.x, off2.y));

  vec4 tex = tex22;

  // Blur
  vec4 blurred = 1.0 * tex00 + 4.0 * tex10 + 6.0 * tex20 + 4.0 * tex30 + 1.0 * tex40
               + 4.0 * tex01 + 16.0 * tex11 + 24.0 * tex21 + 16.0 * tex31 + 4.0 * tex41
               + 6.0 * tex02 + 24.0 * tex12 + 36.0 * tex22 + 24.0 * tex32 + 6.0 * tex42
               + 4.0 * tex03 + 16.0 * tex13 + 24.0 * tex23 + 16.0 * tex33 + 4.0 * tex43
               + 1.0 * tex04 + 4.0 * tex14 + 6.0 * tex24 + 4.0 * tex34 + 1.0 * tex44;
  blurred /= 256.0;

Cảm giác ban đầu của các bạn thế nào? Nhìn khó chịu đúng không? Tôi cũng vậy.

Để giới thiệu qua đoạn code này một tí. Đây là đoạn code viết bằng OpenGL Shading Language, có thể giới thiệu đại khái đây là ngôn ngữ cấp cao (high level language) dành cho việc lập trình các shader trong OpenGL (cái này chắc các bạn lập trình game với OpenGL hiểu rõ hơn, các bạn giúp mình giải thích rõ hơn trong phần comment nhé).

Đoạn code này để làm việc gì?

Đoạn code này được trích ra từ một ứng dụng xem ảnh trên web (tương tự Google Photo), và nhiệm vụ của nó là tính giá trị màu sắc của một điểm ảnh sau khi áp dụng filter, cụ thể ở đây là blur (làm mờ).

Thuật toán đằng sau nó là gì?

Trước khi đi sâu vào đoạn code đó, chúng ta cùng nhìn qua về mặt giải thuật. Để tính được giá trị màu sắc của một điểm trên ảnh sau khi áp dụng filter chúng ta không thể chỉ thay đổi giá trị màu sắc của chính điểm đó bằng cách cộng trừ nhân chia với một giá trị delta nào đó, bởi vì làm vậy thì màu sắc của từng điểm sẽ dễ dàng bị khác biệt và tạo ra cảm giác không đẹp, vì bức ảnh là một tổ hợp của rất nhiều điểm.

Cách giải quyết mà người ta áp dụng vô trường hợp này là thay vì chỉ lấy giá trị màu của một điểm, người ta lấy thêm giá trị màu của những điểm xung quanh nó, cụ thể ở đây là lấy điểm muốn thay đổi màu sắc là tâm và tạo thành một ma trận 5×5 xung quanh nó (như vậy tổng cộng ta lấy 25 điểm), thật ra lấy bao nhiêu điểm cũng được, có thể là 3×3, 7×7, có thể ở trường hợp này 5×5 là giá trị cho ra bức ảnh đẹp nhất. Nghe khó hiểu đúng không, tôi có vẽ hình minh hoạ đây.

Điểm tôi khoanh màu đỏ chính là điểm cần tính và nó có trọng số cao nhất, và những điểm xung quanh có trọng số như tôi viết trong hình. Sau khi đã tính toán được tổng thì ta chia tổng cho 256 để ra giá trị màu thực tế, vì gía trị màu chỉ nằm trong khoảng từ 0 đến 1, và 256 là tổng trọng số của ma trận.

Tương ứng như giải thích ở trên của tôi thì đoạn code bên trên khai báo từ tex00 đến tex44, chính là đoạn code lấy giá trị màu của từng điểm trong ma trận, và bên dưới đó chính là phép tính tổng theo trọng số.

Các bạn hiểu hết ý nghĩa của đoạn code rồi đúng không? Nếu chưa hiểu thì đọc lại lần nữa nhé. Vấn đề chúng ta dễ dàng thấy được trong đoạn code này đó là việc lấy giá trị màu bị lặp đi lặp lại nhiều lần và có thể giải quyết bằng một đoạn code ngắn hơn với việc sử dụng vòng lặp (bao nhiêu bạn nghĩ như vậy khi đọc tới đây thì nhớ comment nhé).

Tôi sẽ viết ví dụ bằng JavaScript cho dễ nhé (vì tôi cũng không rành GLSL lắm). Tương ứng với cách giải quyết bài toán như trên ta có đoạn code sau.

// input (x, y)
const weights = [
  [1, 4, 16, 4, 1],
  [4, 16, 24, 16, 4],
  [16, 24, 36, 24, 16],
  [4, 16, 24, 16, 4],
  [1, 4, 16, 4, 1],
];

let blur = 0;
let totalWeight = 0;

for (let i = 0; i < 5; i++) {
  for (let j = 0; j < 5; j++) {
    const weight = weights[i][j];

    totalWeight += weight;
    blur += weight * getPixelColor(x + i - 2, y + j -2);
  }
}

blur /= totalWeight;

Đoạn code này có gì? Như các bạn thấy tôi khai báo một mảng 2 chiều, tượng trưng cho trọng số của từng điểm trong ma trận. Sau đó tôi dùng vòng lặp để tính toán. Rõ ràng đoạn code như thế này gọn gàng và đẹp đẽ hơn rất nhiều so với việc trải thẳng nó ra như trên.

Câu hỏi đặt ra là code như tôi có tối ưu không?

Câu trả lời là không.

Tại sao?

Quay lại câu chuyện đoạn code bên trên (tức là đoạn code GLSL đó) là đoạn code tương tác đồ hoạ, và được chạy trên GPU, đây chính là vấn đề.

Ủa chứ chạy trên GPU thì sao?

Để nói câu chuyện này, chúng ta phải hiểu sơ qua về cách mà một cái card đồ hoạ hoạt động. Một cái card đồ hoạ sẽ có rất nhiều GPU, và GPU thì làm việc tính toán là tốt nhất. Và việc xử lý rẽ nhánh, câu điều kiện là chuyện mà nó làm không hề tốt bằng CPU, và nhiều khi còn tệ nữa, bởi vì cấu trúc vi xử lý khác nhau.

Trong điều kiện lý tưởng GPU giống như là một đường ống (pipeline) chỉ làm nhiệm vụ là nhận vào input, tính toán, và xuất ra kết quả mà không quan tâm đến việc quyết định nên làm cái gì. Thực tế, vẫn có một số ít card đồ hoạ vẫn optimize chuyện này ở thời điểm compile time, khi thấy vòng lặp nó sẽ tự động trải ra. Nhưng phần lớn các card đồ hoạ đều coi vòng lặp như là một tác vụ rẽ nhánh và phần lớn sẽ chạy rất chậm. Việc trải các câu lệnh ra, là cách mà chúng ta tối ưu, để cho GPU làm việc mà nó làm tốt nhất đó là tính toán, đọc giá trị màu của điểm input.

Như vậy việc tôi vừa nghĩ tới chuyện rút ngắn code bằng vòng lặp là điều vớ vẩn, và chẳng may không chịu tìm hiểu, mà cứ bang bang vào sửa code tạo Pull Request thì có ngày bị ăn chửi.

Kết luận

Việc code đẹp là code tối ưu, hay code xấu là code không tối ưu đều chỉ là tương đối. Để tối ưu code đôi khi ta còn phải xem xét đến rất nhiều yếu tố khác như trong bài viết này đó là đoạn code đó được thực thi ở đâu. Và trên hết, để kết luận được một điều gì đó các bạn hãy cố gắng tìm hiểu thật kĩ trước khi đưa ra bất cứ kết luận gì.

Bài gốc: Codeaholicguy

Bình luận


White
{{ comment.user.name }}
Bỏ hay Hay
{{comment.like_count}}
Male avatar
{{ comment_error }}
Hủy
   

Hiển thị thử

Chỉnh sửa

White

Hoàng Nguyễn

36 bài viết.
453 người follow
Kipalog
{{userFollowed ? 'Following' : 'Follow'}}
Cùng một tác giả
White
89 34
Nhu cầu về Javascript developer hiện nay trong thị trường IT là rất lớn. Nếu bạn có kiến thức ở mảng này thì cơ hội nghề nghiệp cũng như thu nhập c...
Hoàng Nguyễn viết 2 năm trước
89 34
White
51 19
Microservices hiện đang nhận được rất nhiều sự chú ý: các bài viết, các blog, các cuộc thảo luận trên phương tiện truyền thông, trên mạng xã hội, v...
Hoàng Nguyễn viết gần 3 năm trước
51 19
White
41 0
Trong quá trình đi làm, nhất là nếu làm frontend thì chắc chắn sẽ có một lúc nào đó các bạn bị Chrome (trình duyệt nói chung) chửi vô mặt những thứ...
Hoàng Nguyễn viết 2 tháng trước
41 0
{{like_count}}

kipalog

{{ comment_count }}

bình luận

{{liked ? "Đã kipalog" : "Kipalog"}}


White
{{userFollowed ? 'Following' : 'Follow'}}
36 bài viết.
453 người follow

 Đầu mục bài viết

Vẫn còn nữa! x

Kipalog vẫn còn rất nhiều bài viết hay và chủ đề thú vị chờ bạn khám phá!