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

in optimizing •  7 years ago 

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.

sketch.png

Đ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ì.

Nhớ like fanpage hay follow blog nhé, sẽ có nhiều bài viết nữa chờ đón các bạn trong tương lai đó. Và nếu có bất cứ ý kiến hay câu hỏi gì nhớ comment hay hỏi tôi trực tiếp trên fanpage nhé. Chào tạm biệt.

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

Hi! I am a robot. I just upvoted you! I found similar content that readers might be interested in:
https://www.topitworks.com/blogs/chuyen-toi-uu-code-xau-dep-dep-xau/

Congratulations @codeaholicguy! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 1 year!

Click here to view your Board

Support SteemitBoard's project! Vote for its witness and get one more award!

Congratulations @codeaholicguy! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Vote for @Steemitboard as a witness to get one more award and increased upvotes!