Bài 3: Quản lý bộ nhớ tự động

.NET runtime (trình thực thi các ứng dụng .NET) cung cấp khả năng quản lý bộ nhớ tự động thông qua bộ dọn rác (garbage collector – GC). Với bất kỳ ngôn ngữ nào, mô hình quản lý bộ nhớ luôn là một trong những đặc tính quan trọng nhất. Điều này cũng đúng với .NET.

Các lỗi liên quan đến bộ nhớ heap (bộ nhớ nơi chúng ta xin cấp phát và giải phóng – https://daohainam.com/2021/08/14/bo-nho-heap-la-gi/) thường rất khó để debug. Không có gì lạ khi thấy các kỹ sư phải mất hàng tuần, thậm chí hàng tháng trời để có thể dò ra chúng. Nhiều ngôn ngữ dùng một bộ dọn rác (GC) như một cách thân thiện để loại bỏ các bug đó vì GC sẽ đảm bảo quản lý vòng đời cái đối tượng một cách chính xác. Thường thì GC sẽ giải phóng bộ nhớ hàng loạt để hiệu quả hơn. Việc tạm dừng để dọn rác có thể sẽ không phù hợp với các chương trình có yêu cầu rất cao về độ trễ, và bản thân việc sử dụng bộ nhớ có thể sẽ cao hơn. GC có memory locality (1) tốt hơn và một số có khả năng dồn các vùng nhớ giúp nó ít bị phân mảnh (2) hơn.

.NET có một bộ GC có khả năng tự điều chỉnh, hoạt động theo kiểu tracing (3). Nó nhằm mục đích mang lại khả năng vận hành “không cần thao tác” trong những trường hợp thông thường, đồng thời cung cấp các tùy chọn cấu hình với trường hợp khối lượng công việc lớn. GC là kết quả của nhiều năm đầu tư, cải tiến và học hỏi từ nhiều loại khối lượng công việc.

Sơ đồ bộ nhớ .NET

Bump pointer allocation — các đối tượng được cấp phát bằng cách tăng con trỏ phân phối theo kích thước cần thiết (thay vì tìm khoảng trống trong các khối bộ nhớ trống tách biệt) để các đối tượng được cấp phát cùng nhau có xu hướng ở cùng nhau. Và vì các đối tượng thường được truy cập cùng nhau nên điều này cho phép memory locality (1) tốt hơn, điều này rất quan trọng đối với hiệu suất.

Generational collections (các tập đối tượng được chia theo thế hệ) — một điều cực kỳ phổ biến là vòng đời của đối tượng tuân theo giả thuyết về thế hệ, rằng một đối tượng sẽ tồn tại rất lâu hoặc chết rất nhanh. Vì vậy, sẽ hiệu quả hơn nhiều nếu một GC chỉ thu thập bộ nhớ do các đối tượng tạm thời (ephemeral objects) chiếm giữ trong hầu hết thời gian chạy (gọi là ephemeral GC), thay vì phải thu thập toàn bộ heap (gọi là full GC).

Compaction (thu gọn) – cùng một lượng không gian trống nhưng nếu chúng nằm trong ít đoạn lớn sẽ hiệu quả hơn so với nằm trong nhiều phần nhỏ hơn. Trong quá trình thu gọn GC, các đối tượng còn sót lại được di chuyển cùng nhau để có thể hình thành các không gian trống lớn hơn. Hành vi này yêu cầu triển khai phức tạp hơn so với một GC không di chuyển vì nó cần cập nhật các tham chiếu đến các đối tượng đã di chuyển này. .NET GC được điều chỉnh động để chỉ thực hiện nén khi nó xác định bộ nhớ được thu hồi xứng đáng với chi phí GC. Điều này có nghĩa là các tập tạm thời sẽ thường được nén.

Parallel — GC có thể chạy trên một hoặc nhiều thread. Workstation flavor thực hiện GC trên một luồng trong khi Server flavor thực hiện trên nhiều luồng GC để nó có thể hoàn thành nhanh hơn nhiều. GC máy chủ cũng có thể đáp ứng tỷ lệ phân phối lớn hơn vì có nhiều heap để ứng dụng cấp phát dữ liệu trên đó, do vậy nó rất tốt cho throughput (thông lượng).

Concurrent — Thực hiện công việc GC trong khi user thread bị tạm dừng — được gọi là Stop-The-World — giúp việc triển khai trở nên đơn giản hơn nhưng thời lượng của những lần tạm dừng này có thể không được chấp nhận. .NET cung cấp khả năng xử lý đồng thời để giảm thiểu vấn đề đó.

Pinning — .NET GC hỗ trợ ghim đối tượng (khóa lại không cho phép vùng nhớ đó được chuyển sang chỗ khác), cho phép tương tác zero-copy với native code. Khả năng này cho phép tương tác gốc hiệu suất cao và độ trung thực cao, với chi phí hoạt động cho GC được giảm thiểu.

Standalone GC — Một standalone GC có thể được dùng với một implementation khác (được chỉ định qua cấu hình và đáp ứng các yêu cầu về giao diện). Điều này làm cho việc dò lỗi và thử các tính năng mới dễ dàng hơn nhiều.

Diagnostics — GC cung cấp thông tin lượng phong phú về bộ nhớ và các tập hợp, được cấu trúc theo cách cho phép bạn tương quan dữ liệu với phần còn lại của hệ thống. Ví dụ: bạn có thể đánh giá tác động của GC đối với độ trễ bằng cách bắt các sự kiện GC và tương quan chúng với các sự kiện khác như IO để tính toán mức độ đóng góp của GC so với các yếu tố khác, nhờ đó, bạn có thể dành nỗ lực của mình cho các thành phần phù hợp.

Các quá trình quản lý bộ nhớ diễn ra khi chương trình chạy

(1) memory locality: memory locality nói đến việc truy cập vào cùng một phần bộ nhớ trong thời gian ngắn. Nôm na là việc truy cập vào cùng một phần bộ nhớ, hoặc gần với phần bộ nhớ đã truy cập, trong một khoảng thời gian ngắn sẽ nhanh hơn (do tận dụng được cache). Các bạn có thể tham khảo bài viết này: VÌ SAO DUYỆT MẢNG THEO DÒNG LẠI NHANH HƠN THEO CỘT?GIẢI THÍCH: Vấn đề nằm ở CPU cache.

(2) memory fragmentation – phân mảnh bộ nhớ: Quá trình cấp phát/giải phóng sẽ tạo ra các vùng nhớ trống không liên tục (vì các vùng nằm giữa chúng vẫn chưa được giải phóng). Phân mảnh làm cho việc quản lý bộ nhớ không hiệu quả.

(3) tracing GC: tracing GC là kỹ thuật xác định các đối tượng đang được dùng bằng cách dò theo các đối tượng “gốc”. Ví dụ nếu bạn có object Parent, chứa đối tượng Child (thuộc tính Child, hoặc một biến Child), người ta dò từ Parent sẽ biết Child vẫn đang được dùng. Các đối tượng không thuộc “gốc” nào sẽ là các đối tượng cần giải phóng. Khác với tracing GC là reference counting GC – các vùng nhớ sẽ chứa số đếm số lượng biến đang trỏ đến, nếu số lượng này bằng 0 có nghĩa là GC có thể giải phóng vùng nhớ tương ứng.

* Hai hình ảnh trong bài này được lấy từ https://prodotnetmemory.com/

GIẢI THÍCH CÁC KHÁI NIỆM TRONG OOP – TÍNH ĐA HÌNH – phần 2

Trước khi đọc bài này, xin hãy ngẩng đầu lên trời đọc câu sau 3 lần: “Khi tôi học gì thì phải hiểu đến tận gốc rễ!

Hãy đọc qua phần 1 nếu bạn chưa biết đa hình là gì.

Trong phần 2, ta sẽ tìm hiểu virtual❗️, là chìa khóa cho sức mạnh của đa hình, chúng ta cũng sẽ phải đọc một chút ngôn ngữ Assembly. Đây là cách học hại não🏴‍☠️, nhưng nó đáng giá từng phút bạn học, và đảm bảo nếu chịu khó bạn sẽ hiểu thêm được rất nhiều, vậy nên hãy cố lên nhé.

Đọc để biết thêm về Ngôn ngữ Assembly: https://daohainam.com/2023/03/07/the-nao-la-ngon-ngu-lap-trinh-bac-thap-bac-cao/

Trước tiên xin giới thiệu với bạn một công cụ cho phép ta dịch các chương trình sang assembly (hợp ngữ), ta sẽ dùng công cụ này để khảo sát những gì trình biên dịch tạo ra từ chương trình animal.

Bạn truy cập Godbolt tại https://godbolt.org/z/KiMvdD.

Trở lại với chương trình animal, với 2 lớp Dog và Fish. Ta sẽ dịch nó sang mã assembly và xem những gì thực sự xảy ra.

Continue reading “GIẢI THÍCH CÁC KHÁI NIỆM TRONG OOP – TÍNH ĐA HÌNH – phần 2”

.NET là gì? Và vì sao ta nên chọn nó? – Bài 2: Hệ thống kiểu trong .NET

.NET cung cấp một hệ thống kiểu rất rộng, phục vụ gần như ngang nhau cho safety (tính an toàn), descriptiveness (khả năng tự mô tả), dynamism (kiểu động), and native interop (tương tác với các thành phần native).

Trước hết, hệ thống kiểu cho phép một mô hình lập trình hướng đối tượng. Nó bao gồm các kiểu, kế thừa (đơn thừa kế), interface (bao gồm default method implementation (phương thức mặc định)) và virtual method để cung cấp một hành vi phù hợp cho tất cả các phân lớp kiểu mà hướng đối tượng cho phép.

Generics là một tính năng phổ biến cho phép các lớp chuyên biệt hóa thành một hoặc nhiều kiểu. Ví dụ: List<T> là một lớp chung mở, nhờ đó ta có thể viết List<string> và List<int> ta không cần phải tạo thêm các lớp ListOfString và ListOfInt riêng biệt, hoặc phải dựa vào object và truyền như trường hợp của ArrayList. Generics cũng cho phép tạo nhiều hệ thống hữu ích trên các kiểu khác nhau (và giảm nhu cầu sử dụng nhiều code), như với Generic Math.

Continue reading “.NET là gì? Và vì sao ta nên chọn nó? – Bài 2: Hệ thống kiểu trong .NET”

.NET là gì? Và vì sao ta nên chọn nó? – Bài 1

Đây là bài viết được dịch từ https://devblogs.microsoft.com/dotnet/why-dotnet/

Bài viết này nhằm giới thiệu về .NET, một trong những nền tảng lập trình phổ biến nhất, mạnh mẽ nhất, hỗ trợ đầy đủ tất cả các hệ điều hành, kiến trúc, từ smart phone, đến desktop, máy chủ… Với sự hậu thuẫn mạnh mẽ bởi Microsoft và cộng đồng hàng triệu lập trình viên trên khắp thế giới.

Hiểu rõ những khả năng của .NET giúp bạn quyết định việc đầu tư việc học vào nền tảng này.

Phần chữ in nghiêng là phần ghi chú thêm của người dịch.

.NET đã thay đổi rất nhiều từ lúc chúng tôi khởi động dự án chuyển đổi .NET sang mã mở và đa nền tảng. Chúng tôi đã khảo sát lại toàn bộ, tinh chỉnh lại nền tảng, thêm vào các tính năng ở cấp thấp hỗ trợ cho hiệu năng và tính an toàn, cùng với các tính năng ở cấp cao giúp việc xây dựng ứng dụng nhanh chóng và hiệu quả.  Span<T>hardware intrinsics, và nullable reference types là một số ví dụ. Chúng tôi cũng khởi động một blog “.NET Design Point” để giúp bạn biết thêm về những khái niệm và quyết định khi thiết kế ra .NET platform mà chúng ta có ngày nay, và nó đã giúp ích như thế nào khi viết code.

Bài đầu tiên của loại bài này cung cấp một cái nhìn tổng quan về các trụ cột chính và các design point của nền tảng. Nó mô tả ở mức độ khái quát “những gì bạn nhận được” khi bạn chọn .NET và nhằm mục đích trở thành một bộ khung tập trung vào những điểm quan trọng mà bạn có thể dựa trên đó để giới thiệu nền tảng này đến những người khác. Các bài viết tiếp theo sẽ đi vào chi tiết của các chủ đề đã nói đến. Bài viết này sẽ không mô tả về các công cụ, như Visual Studio, hoặc các thư viện cấp cao hoặc các mô hình ứng dụng như ASP.NET Core.

“.NET” mà chúng ta đang nói đến là .NET Core hiện đại. Nếu bạn quên, thì chúng tôi đã bắt đầu dự án này vào năm 2014 như một dự án nguồn mở trên GitHub. Nó có thể chạy Linux, macOS, và Windows on Arm64, x64, và các kiến trúc chíp khác nữa. Nó cũng có sẵn trong nhiều Linux distro. Nó cũng duy trì rất nhiều sự tương thích với .NET Framwork cũ, nhưng nó thực sự là một sản phẩm và là một hướng đi mới.

Continue reading “.NET là gì? Và vì sao ta nên chọn nó? – Bài 1”

TÌM VIỆC CŨNG NHƯ ĐI BÁN HÀNG

Thật vậy, khi đi làm ở một công ty nào đó, tức là bạn đang đi bán sức lao động cho họ và để thu lại tiền. Do vậy về nguyên tắc nó cũng tương tự như khi bạn bán bất kỳ sản phẩm nào khác.

Vậy ta cùng xem qua một số yếu tố có thể ảnh hưởng đến quá trình bán hàng của bạn nhé.

Chất lượng sản phẩm:

Nếu bạn là một sản phẩm tồi, nghĩa là chẳng biết làm gì cả, vậy thì bạn không cần đọc tiếp. Vì khách hàng của bạn, họ biết rất rõ họ cần mua gì, và họ cũng rất có kinh nghiệm trong việc mua sắm nên dù gì cũng sẽ chẳng bán được. Trong thị trường lao động, bạn không thể bán hàng xong là khóa máy, đổi địa chỉ và biến mất, nên dù có lừa được họ để được nhận việc thì sau đó bạn cũng sẽ phải ra đi thôi.

Vậy nên, khi còn đi học hãy cố gắng biến mình thành một sản phẩm tốt để có cái mà chào hàng.

Continue reading “TÌM VIỆC CŨNG NHƯ ĐI BÁN HÀNG”

NHỮNG MÔN HỌC CẦN THIẾT NẾU BẠN MUỐN TRỞ THÀNH SENIOR DEVELOPER

Cấu trúc dữ liệu và thuật toán:

Không cần nói nhiều về môn học này nhỉ? Bạn sẽ cần tìm hiểu về các cấu trúc dữ liệu thường dùng như: danh sách liên kết, cây, stack, queue… hoặc các thuật toán từ đơn giản đến phức tạp như tìm kiếm, sắp xếp, nén dữ liệu, tìm đường đi…

Các cấu trúc bạn học được dùng rất nhiều, khi học các môn khác bạn sẽ cảm thấy khó hiểu nếu không nắm chắc phần này. Ví dụ danh sách liên kết (linked list), stack được dùng trong quản lý bộ nhớ máy tính, queue rất phổ biến trong các hệ thống phân tán, hoặc xử lý dữ liệu không đồng bộ. Cấu trúc cây được dùng rất nhiều trong các hệ cơ sở dữ liệu, không hiểu nó bạn sẽ khó đi sâu vào mảng này.

Các thuật toán cũng vậy, làm quen với các thuật toán là các hiệu quả nhất để hiểu cách máy tính làm việc, cũng như phương pháp giải quyết vấn đề dựa trên máy tính. Trong thực tế bạn sẽ không bao giờ phải làm lại các bài toán trong môn Thuật toán vì chúng luôn có sẵn trong các thư viện của ngôn ngữ lập trình. Tuy nhiên, bạn sẽ phải áp dụng kiến thức của môn học này để giải quyết những vấn đề bạn gặp trong các bài toán thực tế.

Bạn không cần phải quá đi sâu nếu không có ý định dự các cuộc thi. Đừng cố gắng học hết tất cả các thuật toán, làm tất cả các bài tập, chỉ cần hiểu cách chúng làm việc là được. Bởi sẽ còn một núi kiến thức khác bạn cần tiếp thu ngoài môn học này.

Continue reading “NHỮNG MÔN HỌC CẦN THIẾT NẾU BẠN MUỐN TRỞ THÀNH SENIOR DEVELOPER”

Ví dụ về SOLID trong OOP

SOLID là tập hợp 5 nguyên tắc thiết kế các lớp trong OOP, tuân thủ các nguyên tắc này sẽ giúp bạn tạo thiết kế dễ thay đổi, mở rộng, dễ kiểm soát lỗi về sau. Đây là các nguyên tắc mà từ anh fresher tới anh lập trình sư, và cho đến ngày code cuối cùng trước khi xuống lỗ bạn vẫn phải tuân theo (chứ không đến khi con cháu thừa kế lại code ngày nào nó cũng lôi ra chửi 😁).

Vấn đề là làm sao để các bạn hiểu đúng và đầy đủ 5 nguyên tắc này. Tôi đã cố gắng suy nghĩ, tâm tư, tìm hiểu… kể cả lúc đi ăn và đi… ngủ, để tìm xem cách nào giúp các bạn hiểu và nhớ các quy tắc này dễ dàng nhất. Và cách tôi chọn là viết ra 5 ví dụ mẫu, đại diện cho việc vi phạm 5 quy tắc trên.

Các bạn có thể truy cập vào các ví dụ trên tại: https://github.com/daohainam/solid-bad-designs

Cách học là:

– Bạn hãy đọc qua code của từng ví dụ, tốt nhất là theo thứ tự các chữ cái đầu tiên S-O-L-I-D.

– Tự mình suy nghĩ xem có vấn đề gì với thiết kế trên. Bạn nên đặt ra các câu hỏi kiểu như: “Nếu sau này ta muốn thêm”, “Nếu sau này ta muốn thay đổi” … thì phải làm sao?

– Từ đó bạn xem thử khi bạn muốn thêm/thay đổi như vậy thì sẽ gặp vấn đề gì.

– Thử thay đổi lại thiết kế các lớp để giúp thiết kế tốt hơn, giúp giải quyết các vấn đề của bạn.

Chủ đề về phân tích thiết kế là một chủ đề rất thú vị các bạn ạ. Trong thực tế bạn sẽ luôn gặp những vấn đề mà lúc đi học có nằm mơ cũng không tưởng tượng ra được 😀. Bạn sẽ học mãi, học mãi, tìm giải pháp, giải quyết vấn đề, một hôm nào đó lại thấy một vấn đề mới trong giải pháp tưởng chừng hoàn hảo đó, rồi lại học, lại suy nghĩ…

Tôi sẽ cập nhật thêm mô tả các vấn đề và giải pháp. Các bạn nhớ truy cập vào repository và tặng cho nó 1 Star nếu thấy hay nhé, xin cảm ơn trước! 😘

THẾ NÀO LÀ NGÔN NGỮ LẬP TRÌNH BẬC THẤP, BẬC CAO?

Ngôn ngữ lập trình là gì thì chắc ở đây ai cũng biết rồi, vậy nhưng tại sao người ta còn có bậc thấp và bậc cao?

Để ngắn gọn, ta có thể nhớ luôn Hợp ngữ (Assembly language) và ngôn ngữ máy là ngôn ngữ cấp thấp, còn tất cả các ngôn ngữ khác đều là bậc cao.

NGÔN NGỮ MÁY

Máy tính vốn không hiểu tiếng người, bộ nhớ của nó chỉ chứa duy nhất các bit 0 và 1, được gom lại thành từng byte. Việc đọc hay ghi luôn được thực hiện theo đơn vị byte, cũng như việc đánh địa chỉ cũng theo byte. Bạn không thể yêu cầu CPU hay các thiết bị ngoại vi: “Hãy lấy cho tôi 1 bit ở vị trí xyz nào đó”. Muốn làm điều đó bạn phải tính toán xem bit đó thuộc byte nào (cứ chia cho 8 là được), đọc byte đó, rồi xem bit đó tương ứng với vị trí thứ mấy trong byte, dùng một toán tử bit nào đó (AND chẳng hạn) để kiểm tra xem nó bằng 1 hay bằng 0.

Continue reading “THẾ NÀO LÀ NGÔN NGỮ LẬP TRÌNH BẬC THẤP, BẬC CAO?”

NAM! Code vui lắm đó!

Đó là câu nói anh bạn người Thụy Điển nói với mình.

Chuyện là mấy ngày rồi anh bạn này sang Na Uy để hỗ trợ mình tham gia vào một dự án trên mobile, hai người bằng tuổi, lại làm chung với nhau khá lâu rồi nên nói chuyện rất vui. Có lần mình nói về sở thích, anh bảo: tao cứ rảnh không làm gì là lại ngồi code. Nam à, code vui lắm đó, he he. 😂

Yes! Yes! I know! 😁

Nói một chút về dự án này. Đây là một app viết trên Xamarin, back end của nó là cả một gói bự, dùng cho các công ty vận tải hàng hoá, được triển khai trên khoảng 200 server. Mình vốn là một trong những người xây dựng lên những phiên bản đầu tiên của hệ thống, nên thực ra lạ mà quen (lúc đọc lại code phải cố gắng kìm chế để không chửi cái thằng viết, vì biết đâu đó là mình thì sao 😂). Khi đó chưa có docker, microservice ít phổ biến như bây giờ nên mô hình triển khai khá phức tạp, để triển khai trên số server phân tán lớn như vậy bên mình phải có team chuyên tooling để tạo ra các công cụ hỗ trợ.

Continue reading “NAM! Code vui lắm đó!”

Liệu từ một người không biết gì về lập trình thì có khả năng đi làm sau 1 năm không nhỉ?

Nếu lướt qua các hội nhóm lập trình thì câu trả lời có lẽ là không! Riêng tôi câu trả lời cũng là không nốt! 😅

Đùa đấy, thực ra câu trả lời là hoàn toàn được. Những người đã học vài năm có lẽ sẽ cười vào mũi tôi, vì cứ tưởng lập trình là dễ lắm ấy, chắc ông này chưa đi làm bao giờ.

Vậy tôi sẽ hỏi lại một câu khác:

“Liệu từ một người không biết gì về lập trình thì có khả năng đi làm sau 2000 giờ học không nhỉ?”

Continue reading “Liệu từ một người không biết gì về lập trình thì có khả năng đi làm sau 1 năm không nhỉ?”