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
Continue reading “Bài 3: Quản lý bộ nhớ tự động”

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ễ!

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”

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 “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”

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ách 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?”

GIẢI THÍCH: Vấn đề nằm ở CPU cache

Đáp án cho câu hỏi trong bài: https://daohainam.com/2021/12/30/vi-sao-duyet-mang-theo-dong-lai-nhanh-hon-theo-cot/

👉 Khi nằm trong bộ nhớ, các mảng nhiều chiều sẽ được diễn dịch thành một mảng 1 chiều (vì bộ nhớ về cơ bản cũng chỉ là mảng 1 chiều). Mỗi dòng sẽ được sắp xếp liên tục theo thứ tự. Mỗi khi cần truy xuất đến 1 ô nào đó có địa chỉ m[dòng, cột], trình biên dịch sẽ biến đổi thành m[dòng * chiều rộng mảng + cột] (xem hình minh họa).

👉 Như vậy, nếu ta đi chuyển theo dòng->cột (tương ứng với calculate_sum(sum, 1) trong https://github.com/…/clanc…/blob/master/CachingTests.cpp), thứ tự truy xuất trong bộ nhớ sẽ được tăng dần, trong khi đó, nếu ta di chuyển theo cột->dòng, thứ tự truy xuất theo hình minh họa sẽ là 0, 4, 8, 1, 5…

👉 Bộ nhớ cache trong CPU được tổ chức theo từng lance (không biết dịch ra thế nào, trong tiếng Việt ta vẫn dùng từ lance để chỉ các phần đường phân cách nhau). Mỗi lance có kích thước 64 byte, mỗi khi nạp từ RAM vào cache, hay từ cache vào RAM nó sẽ luôn làm việc với từng lance như vậy. Do đó khi đọc vào 1 byte, tất cả các byte lân cận trong cùng lance sẽ nằm sẵn ngay trong cache, khi bạn đọc đến byte kế tiếp bạn chỉ cần lấy nó ra từ cache (cache hit). Tốc độ của cache lại nhanh hơn RAM rất nhiều, người ta tính toán rằng cache L1 trong CPU có tốc độ nhanh hơn vài chục tới cả trăm lần so với truy xuất từ RAM.

👉 Kết quả là việc đọc/ghi dữ liệu tuần tự sẽ cho tốc độ tốt hơn nhiều so với truy xuất ngẫu nhiên. Điều này cũng tương tự như khi bạn đọc dữ liệu từ ổ SSD, vốn không có các cơ cấu cơ học và không có thời gian di chuyển đầu đọc như HDD, tuy nhiên khi copy 1 file lớn vẫn nhanh hơn nhiều so với copy nhiều file nhỏ. Đó cũng là do khi đọc/ghi tuần tự thì xác suất cache hit sẽ lớn hơn nhiều so với cache miss.❗️Khi làm việc với các ứng dụng lớn, việc tổ chức cách lưu trữ dữ liệu rất quan trọng!

VÌ SAO DUYỆT MẢNG THEO DÒNG LẠI NHANH HƠN THEO CỘT?

Mình vừa viết một chương trình nhỏ, chỉ để tính tổng các ô trong một ma trận, tuy nhiên khi thử duyệt theo dòng thì luôn thấy nhanh hơn cột, mảng càng lớn tốc độ càng khác biệt.Các bạn có thể tải về chương trình tại ( https://github.com/namdotnet/clancetest) và chạy thử xem có đúng không, và mất bao nhiêu tick mỗi bước, laptop mình đang dùng chạy Xeon mất hơn 600 ticks cho bước 1.

Nếu nhiều người ủng hộ thì mình sẽ giải thích lý do tại sao 😉(ghi chú là trong ví dụ này mình gọi dòng trước cột sau nhé int m[ROWS][COLS]).

TỪ NGÔN NGỮ LẬP TRÌNH ĐẾN MÃ MÁY (phần 2)

Sau khi đã hoàn thành tiền xử lý, chúng ta sẽ sang bước thứ hai: Biên dịch.

Về cơ bản quá trình biên dịch nhận đầu vào là file mã nguồn, phân tích nó và xuất ra file nhị phân chứa mã thực thi. Quá trình này sẽ chia làm 3 bước chính:

👉 Phân tích từ vựng (lexical analysis)Trình biên dịch sẽ đọc file mã nguồn, từng ký tự một, sau đó phân tích các ký tự rời rạc đó thành các từ vựng trong bộ từ vựng của ngôn ngữ tương ứng.

Xét ví dụ sau:

int x = myfunc(x) * 10

Ví dụ trên sẽ được phân tích thành các từ vựng (ta có thể dùng từ tiếng Anh là token): int, x, =, myfunc, (, x, ), *, 10.Trình phân tích từ vựng sử dụng các ký tự phân tách (khoảng trắng, tab, xuống dòng, dấu chấm phẩy… tùy ngôn ngữ) để tìm ra các từ vựng, nó cũng biết rằng chữ n và t nằm sau i phải được kết hợp thành keyword int, ký tự 1 và 0 phải được kết hợp thành một nhóm là 10 – và đây là một số nguyên, tương tự cho các token còn lại.

Continue reading “TỪ NGÔN NGỮ LẬP TRÌNH ĐẾN MÃ MÁY (phần 2)”