2 điểm bởi GN⁺ 2024-10-20 | 1 bình luận | Chia sẻ qua WhatsApp

-.NET 9.0 giảm mạnh thời gian thực thi so với .NET 8 trong nhiều kịch bản LINQ phổ biến, và trong một số benchmark còn loại bỏ cả cấp phát bộ nhớ

  • Một trong những cải tiến cốt lõi là khi duyệt mảng hoặc List<T>, nó lấy ReadOnlySpan<T> qua TryGetSpan() để giảm chi phí lặp
  • TryGetSpan() nhận diện TSource[]List<TSource> bằng so sánh kiểu, nhưng cách lấy span từ mảng nội bộ của List<T> là một tối ưu hóa thuộc nhóm Unsafe có thể bị vô hiệu khi dung lượng thay đổi
  • LINQ trong .NET 9 nhận diện các chuỗi gọi phổ biến để tạo iterator chuyên biệt, đồng thời áp dụng tối ưu hóa bổ sung ở các phương thức kết thúc như Count(), First(), Last(), ElementAt(), Sum()
  • Chỉ cần di chuyển phiên bản và biên dịch lại là đã có thể nhận được một phần cải thiện hiệu năng LINQ, cùng với các tối ưu như tận dụng SIMD và phát hiện sớm chuỗi rỗng

Vì sao việc duyệt mảng và danh sách nhanh hơn

  • Benchmark đầu tiên lưu Enumerable.Range(1, 10_000).ToArray() dưới dạng IEnumerable<int>, rồi chạy Count, All, Any, First, Single, Last để so sánh .NET 8 và .NET 9
  • Sử dụng BenchmarkDotNet, và dự án phải nhắm tới net8.0;net9.0 rồi biên dịch ở chế độ Release
  • Trong .NET 9, thời gian thực thi của nhiều phương thức giảm mạnh và cấp phát cũng biến mất
    • LinqCount: từ 16,198.490 ns xuống 3,043.563 ns, từ cấp phát 32 B xuống không còn cấp phát
    • LinqAny: từ 17,096.735 ns xuống 2,483.927 ns, từ cấp phát 32 B xuống không còn cấp phát
    • LinqFirst: từ 15,289.747 ns xuống 2,243.341 ns, từ cấp phát 32 B xuống không còn cấp phát
    • LinqSingle: từ 21,684.114 ns xuống 4,884.329 ns, từ cấp phát 32 B xuống không còn cấp phát
    • LinqAll: từ 10.588 ns xuống 2.562 ns, từ cấp phát 32 B xuống không còn cấp phát
    • LinqLast: từ 15.967 ns xuống 6.918 ns

Khác biệt do TryGetSpan() tạo ra

  • Nguyên nhân chính của mức tăng hiệu năng là việc dùng TryGetSpan()
  • Nếu enumerable cần duyệt là mảng hoặc danh sách, TryGetSpan() sẽ trả về ReadOnlySpan<T> để cho phép lặp nhanh hơn
  • Đoạn nhánh cốt lõi kiểm tra source.GetType() == typeof(TSource[]) hoặc source.GetType() == typeof(List<TSource>), sau đó lấy span
    • Mảng được xử lý bằng Unsafe.As<TSource[]>(source)
    • Danh sách dùng CollectionsMarshal.AsSpan(Unsafe.As<List<TSource>>(source)) để lấy span từ mảng nội bộ
  • Trong mã nguồn, source.GetType() được gọi hai lần và không xử lý bằng ép kiểu rồi kiểm tra null, nhưng đây là lựa chọn có cân nhắc của các chuyên gia hiệu năng .NET dựa trên tối ưu của trình biên dịch C# và JIT
  • Trong stack .NET đã được tối ưu hóa rất cao, vi tối ưu hóa có thể cho ra kết quả khác với vẻ bề ngoài

Các ràng buộc của CollectionsMarshal.AsSpan()

  • List<TSource> về bản chất tham chiếu đến một mảng, và khi cần tăng hoặc giảm dung lượng, danh sách sẽ tạo mảng mới rồi tham chiếu sang đó
  • CollectionsMarshal.AsSpan(Unsafe.As<List<TSource>>(source)) lấy Span<TSource> từ chính mảng nội bộ này
  • Nếu dung lượng của danh sách thay đổi theo bất kỳ cách nào, mảng lấy được bằng cách này có thể bị vô hiệu hóa
  • Vì ràng buộc đó, một số thao tác Enumerable có duyệt trì hoãn như yield khó có thể dựa vào tối ưu hóa này
  • Chính cái tên System.Runtime.CompilerServices.Unsafe đã cho thấy rủi ro đó

Phạm vi gọi TryGetSpan()

  • Dùng NDepend để quét System.Linq.dll và xác định các caller trực tiếp/gián tiếp của TryGetSpan()
  • Đường dẫn assembly được phân tích là C:\Program Files\dotnet\shared\Microsoft.NETCore.App\9.0.0-rc.1.24431.7\System.Linq.dll
  • Từ TryGetSpan(), tạo truy vấn mã để nhận diện caller, rồi xuất 56 phương thức khớp ra đồ thị phụ thuộc
  • Nhiều phương thức Enumerable tiêu chuẩn sẽ thử duyệt bằng span khi collection là mảng hoặc danh sách
  • Tuy vậy, vì cách giữ mảng nội bộ của danh sách không an toàn, các phép toán cần thực thi trì hoãn vẫn còn bị hạn chế

Tối ưu hóa dựa trên iterator chuyên biệt

  • Benchmark thứ hai dùng ví dụ trong PR Consolidate LINQ’s internal IIListProvider/IPartition into base Iterator class
  • Các trường hợp được thử gồm Distinct().First(), Append().Select().Last(), Reverse().Count(), DefaultIfEmpty().Select().ElementAt(), Skip().Take().ElementAt(), Union().First(), Select().Where().Select().Sum()
  • Trong .NET 9, một số chuỗi gọi nhanh lên rất mạnh
    • DistinctFirst: từ 65.318 ns xuống 11.192 ns, từ cấp phát 328 B xuống không còn cấp phát
    • AppendSelectLast: từ 4,122.007 ns xuống 2.661 ns, từ cấp phát 144 B xuống không còn cấp phát
    • DefaultIfEmptySelectElementAt: từ 4,090.818 ns xuống 5.724 ns, từ cấp phát 144 B xuống không còn cấp phát
    • RangeUnionFirst: từ 66.309 ns xuống 6.193 ns, từ cấp phát 344 B xuống không còn cấp phát
    • ListSkipTakeElementAt: từ 6.268 ns xuống 2.916 ns
    • RangeReverseCount: từ 11.024 ns xuống 6.134 ns
  • Ngược lại, SelectWhereSelectSum chậm hơn, từ 3,959.622 ns trên .NET 8 lên 4,460.008 ns trên .NET 9, và vẫn giữ cấp phát 112 B

Nhận diện các chuỗi LINQ phổ biến

  • Nhóm hiệu năng .NET đã thiết kế mã để nhận diện các chuỗi gọi LINQ phổ biến
  • Khi phát hiện một chuỗi cụ thể, hệ thống sẽ tạo iterator chuyên biệt để xử lý luồng công việc hiệu quả hơn
  • Nếu chuỗi kết thúc bằng các phương thức như Count(), First(), Last(), ElementAt(), Sum(), có thể áp dụng thêm tối ưu hóa
  • Ví dụ, OrderBy(criteria).First() có thể được tối ưu để chạy như Min(criteria)

Cấu trúc Iterator<T> và các lớp dẫn xuất

  • Bên trong LINQ có lớp cơ sở trừu tượng Iterator<T> và 40 lớp dẫn xuất
  • Tất cả các lớp này đều được lồng bên trong lớp Enumerable
  • Iterator<T> là lớp trừu tượng nhưng các phương thức là virtual, nên các lớp dẫn xuất chỉ override những phương thức cần thiết
  • Cấu trúc này là nền tảng để chứa hành vi chuyên biệt theo từng chuỗi gọi

Trường hợp ListWhereSelectIterator<TSource, TResult>

  • ListWhereSelectIterator<TSource, TResult> xử lý chuỗi Where(...).Select(...) trên danh sách bằng một iterator duy nhất
  • Iterator này được tạo trong Select() override của ListWhereIterator<TSource, TResult>
  • ListWhereIterator<TSource> được tạo khi Enumerable.Where() kiểm tra thấy nguồn là List<TSource>
  • ListWhereSelectIterator<TSource, TResult> không override các phương thức như TryGetFirst() hay TryGetLast()
  • Điểm mấu chốt của cải thiện hiệu năng là trên danh sách, chuỗi rất phổ biến Where(...).Select(...) được gộp thành một iterator thay vì hai iterator
    • Bên trong MoveNext(), hai delegate _predicate_selector được gọi cùng nhau

Trường hợp IListSkipTakeIterator<TSource>

  • IListSkipTakeIterator<TSource> là iterator chuyên biệt được tạo ra khi có thể áp dụng
  • MoveNext() dùng _state - 1 làm chỉ số 0-based của danh sách
  • Dùng một trường chỉ số riêng sẽ dễ đọc hơn, nhưng để giảm kích thước trường của iterator, giá trị được lưu lệch vào _state
  • Tối ưu hóa của iterator này nằm ở việc không duyệt thừa các phần tử nằm ngoài khoảng _minIndexInclusive_maxIndexInclusive

Các tối ưu bổ sung chỉ nhờ di chuyển phiên bản

  • Trong .NET 9, nhiều kịch bản LINQ phổ biến trở nên nhanh hơn
  • Để nhận được các cải tiến từ phiên bản .NET mới, việc cần làm chỉ là di chuyển phiên bản và biên dịch lại
  • LINQ cũng được tối ưu bằng các cách khác
    • Với các trường hợp như cộng tổng chuỗi số nguyên, nó dùng SIMD ở những nơi có thể
    • Các chuỗi rỗng được phát hiện sớm để giảm chi phí liệt kê
  • DeepDotnet videos là tài liệu học .NET có sự xuất hiện của Scott Hanselman và Stephen Toub

1 bình luận

 
GN⁺ 2024-10-20
Ý kiến trên Hacker News
  • Tôi cho rằng phần hữu ích nhất của LINQ không phải là cấu trúc mở rộng dựa trên cây cú pháp của IQueryable, cũng không phải cú pháp tích hợp trong ngôn ngữ, mà là các phương thức mở rộng IEnumerable
    Trước đây nó được gọi hơi dễ gây nhầm lẫn là “LINQ to Objects”, và giúp viết C# theo phong cách hàm một cách ngắn gọn
    Bài gốc chủ yếu nói về việc tối ưu hóa các phương thức mở rộng này
    Chỉ sau khi học Haskell tôi mới thực sự cảm nhận được cách làm này, và nó cũng chia sẻ một phần các điểm mạnh lẫn cạm bẫy của Haskell như đánh giá lười
    Nếu dùng bừa bãi thì có thể tạo ra mã khó hiểu và chậm, nên tôi không muốn khuyến nghị nếu trong nhóm không có ai biết các idiom hàm cơ bản và đánh giá lười

    • Tôi cũng thích khía cạnh hàm của các phần mở rộng LINQ cho IEnumerableIQueryable
      Dễ suy luận hơn, và trong những nơi như Entity Framework thì không phải lúc nào cũng là lựa chọn nhanh nhất, nhưng nhìn chung là một lựa chọn khá ổn
      Tôi cũng thích dùng Dapper hơn EF
      Tuy vậy, các dự án C# có xu hướng có quá nhiều tầng trừu tượng đến mức phi lý, và phát triển “enterprise” nhìn chung khá khó chịu
    • Tôi cũng dùng LINQ theo cách này
      Có vài chỗ đặt tên hơi không theo chuẩn, nhưng có đủ mọi thứ cần thiết
      Eric Lippert đã viết một loạt bài rất hay giải thích monad gắn với LINQ: https://ericlippert.com/2013/04/02/monads-part-twelve/
    • Tôi luôn chỉ dùng cú pháp phương thức của LINQ
      Tôi không thích việc có thêm một ngôn ngữ “tích hợp” khác bên trong ngôn ngữ chủ, và kết quả cuối cùng rốt cuộc cũng phải quay về C#
      Ngay cả khi không dùng ORM như Entity Framework hay Dapper, tôi vẫn thích đặt logic truy cập dữ liệu, bao gồm SQL, trong một dự án được trừu tượng hóa riêng
      Như vậy nó không lan ra toàn bộ ứng dụng, và có thể thay thế khi cần một RDBMS khác
      Dù trong 20 năm thực tế chỉ có đúng một lần như vậy
      Khi lập trình viên junior dùng LINQ, gắn profiler và debugger cho họ sẽ giúp hiểu chuyện gì đang diễn ra bên trong
      Đôi khi cũng hữu ích khi để họ viết trước bằng vòng lặp for và logic C# thông thường rồi so sánh với bản triển khai LINQ, để thấy ưu nhược điểm của hai cách tiếp cận
    • Nếu thích Haskell, bạn có thể cũng sẽ thích những cách dùng khác như xây dựng parser combinator bằng cú pháp truy vấn của LINQ
      Cú pháp truy vấn không được hard-code riêng cho IEnumerable; đó chỉ là hành vi mặc định, và gần như có thể dùng ở bất cứ đâu
      Nó hoạt động hơi giống operator overloading
      [1]: https://github.com/acple/ParsecSharp/blob/da8d0cb9ec39e28dd9...
    • Nếu cú pháp LINQ biến mất vào ngày mai thì tôi cũng không tiếc lắm, nhưng hợp thành hàm thực sự rất mạnh và cũng dễ bảo trì hơn
  • Tôi không hiểu vì sao nhóm dotnet không đầu tư nhiều tài nguyên và thời gian hơn vào công cụ
    Cần có doctest và sinh tài liệu, khả năng viết unit test tốt hơn và nhanh hơn ngay cạnh mã thật, khả năng truy cập mã nguồn, môi trường mà khi nhấn F12 không cần decompile DLL, và một hub trung tâm cho package và tài liệu như pkg.go.dev hay docs.rs
    Phần lớn package NuGet hoặc hoàn toàn không có tài liệu, hoặc chỉ có README trên GitHub, hoặc một wiki ngắn
    Các môi trường khác như Rust, Go, Java, Python đi trước vài năm ánh sáng ở mặt này

    • Tôi bắt đầu đùa rằng Microsoft đầu tư vào OpenAI vì đó là cách duy nhất hợp lý để tra cứu tài liệu package .NET/NuGet
      Nhưng điều kinh khủng là tôi cũng cảm thấy chuyện đó gần với sự thật
    • Hiện tài liệu Microsoft đã có liên kết đi thẳng tới mã nguồn của phương thức bạn đang xem
      Ví dụ: https://learn.microsoft.com/en-us/dotnet/api/system.string.s...
      Mã nguồn của package NuGet cũng có thể làm dễ dàng nếu bật Source Link, nhưng đây vẫn là tính năng tương đối mới nên chưa phải package nào cũng áp dụng
    • Đồng ý, nhưng khả năng cao nguyên nhân cũng là việc C# mã nguồn mở còn tương đối gần đây
      Tôi đoán phần lớn mã C# hiện vẫn được viết trong doanh nghiệp dưới dạng mã nguồn đóng
      Nếu Microsoft tiếp tục đi theo hướng cởi mở như vài năm gần đây, tôi nghĩ mọi thứ sẽ dần tốt hơn theo thời gian
      Một số tính năng này được các công cụ như Resharper cung cấp, nên tôi tự hỏi liệu có thỏa thuận rõ ràng hay ngầm hiểu nào về việc không lấn sân nhau hay không
      Thành thật mà nói, phần lớn tài liệu tôi thấy trong các dự án C# có chất lượng thấp, nên cuối cùng vẫn phải xem mã nguồn
      Kinh nghiệm của tôi là dù có nhiều công cụ tự động hoàn thành, chúng không giúp ích mấy cho việc đọc, mà chỉ giúp cho việc viết
    • Sandcastle Help File Builder đã tồn tại từ rất lâu, và theo tôi nhớ thì bắt đầu như một dự án nội bộ của Microsoft, nhưng kỳ lạ là rất ít thư viện dùng nó
      https://github.com/EWSoftware/SHFB
    • Cũng có cách viết test cạnh mã: https://clipperhouse.com/go-test-csharp/
      Tôi không chắc có nên khuyến nghị không
      Tôi đã thử rồi quay lại cách cũ, vì test dường như chạy lâu hơn, có lẽ do caching artifact build trở nên kém hơn
  • Nói chính xác thì không nên gọi là “cải thiện hiệu năng LINQ” mà là “cải thiện hiệu năng triển khai List của riêng họ”
    Có vẻ Microsoft dành thời gian cải thiện những phần họ cần hơn là các cải tiến nói chung
    LINQ, đặc biệt là cú pháp truy vấn chứ không phải các extension method, cần được đầu tư
    Chủ yếu cần giảm việc cấp phát lambda, và nếu có thể thì rút gọn lambda ở thời điểm biên dịch
    Cần có lambda cục bộ kiểu giá trị, hoặc một chiến lược để việc cấp phát lambda không còn là overhead như hiện nay
    Biến LINQ cũng nên được hỗ trợ wildcard (_) từ lâu rồi, nhưng khi đưa vào lambda thì lại bị bỏ qua hoàn toàn
    Ngoài ra, ở mục cuối của biểu thức LINQ, cần có thể dùng các kiểu đã được lifting như IEnumerable, Option thay cho select ...
    Trong một số use case, select tạo ra overhead không cần thiết, và còn hạn chế những thứ như biểu thức LINQ đệ quy đuôi
    Những thư viện đặt cược hoàn toàn vào LINQ như thư viện của tôi, nhưng không dùng IEnumerable, IQueryable hay LINQ extension, vẫn tiếp tục bị phớt lờ
    Lý do là Microsoft chỉ tập trung cải thiện hiệu năng cho các dự án của chính họ
    Một ví dụ điển hình là suy luận lambda đã được cải thiện
    Nó được đẩy nhanh vì ASP.NET Core Minimal API cần đến
    Có vẻ khá nhiều tính năng ngôn ngữ và framework được thúc đẩy bởi nhu cầu nội bộ của Microsoft hơn là nhu cầu của cộng đồng
    Điều tệ nhất là tập hợp các phương thức ma thuật như GetAwaiter, không chỉ các LINQ extension Select, SelectMany, Where, cứ tiếp tục phình ra
    Thay vì đưa vào các đặc tính higher-kinded thật sự cần thiết để hóa giải sự “ma thuật” này, Microsoft lại thêm tính năng vì lợi ích của chính họ, chủ yếu là vì trình biên dịch
    Vì vậy mọi thứ vẫn ở trạng thái kiểu yếu, và trình biên dịch chỉ có thể phát hiện một cách đại khái
    LINQ là một trong những điểm khác biệt cốt lõi giữa các ngôn ngữ, nhưng gần như bị bỏ mặc kể từ C# 3
    Thật đáng tiếc khi đến giờ họ vẫn xem LINQ chỉ hữu ích cho việc duyệt danh sách, đặc biệt là duyệt triển khai danh sách của chính họ
    Bản thân các cải thiện hiệu năng thì đáng cảm ơn và sẽ giúp ích cho nhiều người dùng, nhưng trọng tâm lúc nào cũng quá hẹp, làm hạn chế tiềm năng
    [1] https://github.com/louthy/language-ext/

    • Nếu có phản hồi hữu ích thì tốt nhất nên mở issue hoặc gửi PR lên dotnet/runtime
      Nhiều cải thiện hiệu năng LINQ được nói trong bài đã được đưa vào theo cách đó
    • Thư viện trông rất thú vị, nhưng cũng có vẻ đã tự đặt mình vào một hình thức dễ bị phớt lờ ngay từ đầu
      Có nhiều câu lệnh using, điều này không phải vấn đề lớn với những người hiểu cách chia nhỏ dự án theo đơn vị cần thiết và tách biệt mối quan tâm
      Nhưng phần lớn lập trình viên không cấu trúc dự án như vậy, và những chi tiết nhỏ này có thể trở thành rào cản với lập trình viên trung bình
      Các lập trình viên junior thường đã gặp khó với cú pháp và phương thức LINQ chuẩn, đặc biệt là cả về hiệu năng
      Việc README nhắc đến những điểm này là tốt
      Thường thì các thư viện chỉ bận “bán” mình, nên tôi thích việc bạn thật sự ghi rõ nó mạnh ở đâu và mục đích là gì
      Việc nói rằng nó không theo thông lệ cũng có thể là vấn đề với người học C#/.NET
      Microsoft có lẽ muốn công cụ và ngôn ngữ tuân theo một số thông lệ nhất định, và cách đặt tên tự nhiên theo lập trình hàm có thể bị Microsoft xem là trở ngại khá lớn khi cân nhắc cải tiến
      Tôi đã star repository này và rất quan tâm đến những gì bạn đã làm
      Trong vài ứng dụng lớn gần đây, tôi đã dùng một kiểu Result trông đại khái giống Option
      Nhưng khi xem lại thư viện, tôi nhận ra dù từng nghĩ mình khá hiểu lập trình hàm, thực ra không phải vậy
      Tôi khá giỏi C# và đã làm những việc phức tạp, nhưng lập trình hàm dù đọc nhiều vẫn khó nắm bắt, và F# For Fun And Profit là thứ giúp tôi hiểu rõ nhất
      Tóm lại, điều đó không có nghĩa là bạn đang làm sai điều gì
      Microsoft sẽ nhắm đến nhóm lập trình viên trung bình hoặc mới bắt đầu, vốn chiếm phần lớn hệ sinh thái của họ
      Tôi hy vọng thư viện này có thể nhận được các cải tiến nội bộ
      Có thể thấy bạn đã bỏ ra rất nhiều thời gian, và chỉ riêng số sao trên GitHub cũng đủ là tín hiệu rằng có người thật sự dùng và được nó giúp ích
      Nếu nghe như tôi đang coi thường thì xin lỗi, nhưng công trình bạn làm rất thú vị và có vẻ tài liệu cũng đủ để tôi từ từ học các khái niệm mình chưa biết
  • C# càng vay mượn nhiều hơn từ F# thì càng tốt
    Tôi đang chờ discriminated union cuối cùng cũng được đưa vào C# để có thể mô hình hóa domain một cách đúng đắn

    • Thật thú vị khi những chuyện như vậy thường xuyên được nhắc đến trong cộng đồng .NET
      Mỗi lần như thế tôi lại tự hỏi: “Sao không dùng luôn F#?”
      C# đã phải đuổi theo trong nhiều năm
      Nếu F# đang thúc đẩy nhiều đổi mới trong hệ sinh thái .NET và đã đi trước vài năm về mặt tính năng, tôi tự hỏi tại sao mọi người không thưởng cho nỗ lực đó bằng cách sử dụng nó
      Nếu có một hướng phát triển ngôn ngữ mà bạn mong muốn, bạn nên khuyến khích nó bằng lựa chọn thực tế
      Trong hệ sinh thái Java, cách đó đã có tác dụng, và giờ Java cũng đang được cải thiện
      Khi thị trường lớn hơn, có thể tạo ra vòng phản hồi tích cực khiến nỗ lực kỹ thuật cũng tăng lên
      Đọc các diễn đàn nhiều năm, tôi có cảm giác phe C# rất muốn “cứ” ở lại phe mình và chờ đợi
      Trông hơi giống chủ nghĩa bộ lạc, như thể đội của họ là “C#”
      Tôi không thấy văn hóa này nhiều ở các hệ sinh thái ngôn ngữ khác, và có cảm giác nếu F# nằm trong một hệ sinh thái khác ngoài .NET thì có lẽ nó đã phát triển mạnh từ lâu
    • Tôi cũng rất muốn có kiểu đơn vị đo lường
      Nó sẽ giúp việc bảo trì code kỹ thuật hoặc khoa học dễ dàng hơn rất nhiều
    • Dùng OneOf[0] và Dunet[1] thì đã có thể đưa discriminated union vào khá dễ dàng
      Ví dụ sử dụng thực tế: https://chrlschn.dev/blog/2024/07/csharp-discriminated-union...
      [0] https://github.com/mcintyre321/OneOf
      [1] https://github.com/domn1995/dunet
    • Việc F# là sân thử nghiệm cho các tính năng của C# và VB.NET là một bí mật ai cũng biết, và các nhân vật chính thức như Hanselman cũng đã nhiều lần nhắc đến
  • Điều tôi nhớ nhất khi làm việc với ngôn ngữ hay hệ sinh thái khác là LINQ
    Việc có những tính năng như vậy trong thư viện chuẩn thật sự rất tuyệt, và nó được thiết kế đẹp trong các ràng buộc sẵn có

  • Trong bài viết thường niên dài cỡ một cuốn sách nói về mọi cải tiến hiệu năng của .NET 9 có một phần liên quan
    https://devblogs.microsoft.com/dotnet/performance-improvemen...
    Lạ là HN không cho phép gửi lại, nên bài viết không lên được front page và bị chìm

  • Khi đã quen với LINQ, và thường làm việc trong những domain nơi LINQ tỏa sáng, bạn sẽ không muốn quay lại cách khác nữa

    • Hỡi các bạn, đừng nghiện LINQ
      LINQ sẽ níu giữ bạn, và khiến bạn oán trách những môi trường không có LINQ
    • Dù vậy nó vẫn kém mạnh hơn những thứ như polars
  • Tôi tò mò liệu có cuốn sách hay tutorial tổng hợp nào tốt để học phát triển web end-to-end bằng dotnet không
    Những thứ tôi tìm thấy phần lớn hoặc quá cơ bản, hoặc đã cũ, hoặc chất lượng thấp

    • Thứ mới và nóng hiện nay trong phát triển web .NET là Blazor, nhưng ngoài phạm vi blog của Microsoft thì nó không phổ biến lắm, và có vẻ sau này cũng sẽ không như vậy
      Cá nhân tôi nghĩ nó sẽ đi theo con đường của Silverlight
      Các công nghệ cũ hơn vẫn có trong .NET 9, vẫn hoạt động và được bảo trì
      Ngày nay làm web bằng .NET nhìn chung là tạo HTTP/JSON/REST API rồi kết nối với framework frontend mà bạn muốn
      Trường hợp của tôi thì dùng React hoặc NextJS
      Từ khóa tìm kiếm nên là ASP.NET WebApi, hoặc hiện đại hơn là ASP.NET Minimal API
      Bạn vẫn có thể render phía server bằng .NET MVC với Razor
      Vì đây là ngôn ngữ markup của ASP.NET MVC, nên cứ tìm “ASP.NET MVC Razor” là được
    • Gần đây tôi bắt đầu quan tâm đến phát triển web bằng C#
      Dù tốt hay xấu, cách làm web application trong .NET về cơ bản có vẻ là ASP.NET
      Việc thiếu lựa chọn thay thế thì hơi đáng ngờ
      Tôi đã nghe một podcast có Andrew Lock, tác giả của “ASP.NET Core in Action”, và ông ấy có vẻ là người rất hiểu vấn đề
      Tôi chưa đọc sách, nhưng có thể đó là cuốn bạn đang tìm
      1: https://dotnetcore.show/season-6/navigating-the-aspnet-core-...
      2: https://www.manning.com/books/asp-net-core-in-action-third-e...
    • Hơi ít mainstream, nhưng tổ hợp F# và Fable rất mạnh
      Ở phía server, bạn có thể chạy Giraffe trên ASP.NET; đó là một lớp lập trình hàm có hiệu năng tương tự C#
      Ở frontend, bạn có thể viết React bằng một ngôn ngữ lập trình hàm thật sự
      Tất nhiên cũng có thể chia sẻ mã F# giữa frontend và backend
    • Tôi học bằng cách tự làm, nhưng xin liệt kê vài tài liệu đáng tham khảo
      Về sách thì có “C# 12 and .NET 8 - Modern Cross-Platform Development Fundamentals - Eighth Edition: Start building websites and services with ASP.NET Core 8, Blazor, and EF Core 8” của Mark J Price, và “Web API Development with ASP.NET Core 8: Learn techniques, patterns, and tools for building high-performance, robust, and scalable web APIs” của Xiaodi Yan
      Tutorial thì có các series của IAmTimCorey và Shawn Wildermuth trên YouTube
    • Nếu là UI render trên server, hãy tìm tài liệu dùng Razor, và ban đầu nên tránh tài liệu về Blazor
      Nếu là tổ hợp backend .NET và frontend JS, hãy tìm tài liệu dùng Minimal API
      MVC cũng tốt, nhưng gánh nặng tương thích ngược nhiều nên Minimal API mới ra đời
  • Phải có cách nào tốt hơn đống mì chú thích này chứ
    Mỗi lần nhìn mã .NET hiện đại là tôi đau mắt

    • Những attribute đó tương ứng với thư viện benchmarking được dùng trong bài
      Mã unit test và benchmarking thường trông hơi spaghetti ở một mức nào đó
      Dù vậy, nếu PR trong business logic thực tế mà như thế thì tôi sẽ không duyệt
      Nếu thật sự ghét, bạn vẫn có thể dùng những thứ như AspNetCore mà không đụng đến attribute nào cả
    • Không rõ bạn đang nhìn mã .NET nào
      Tôi gần như không dùng attribute
  • Phần nói rằng có thể tối ưu thêm khi chain kết thúc bằng các phương thức như Count(), First(), Last(), ElementAt(), Sum(), chẳng hạn OrderBy(criteria).First() có thể được tối ưu để chạy giống Min(criteria), có thể hữu ích
    Nhưng ngay từ đầu nên viết mã tốt hơn
    Điều này có thể thú vị với các chain được tạo động, nhưng nếu là mã viết tay mà làm các thao tác như vậy thì tôi thấy giống một kiểu củng cố tích cực lệch lạc
    Thư viện đang nhận diện và sửa các pattern kém hiệu quả
    Ít nhất tôi hy vọng có phản hồi đề xuất cải thiện mã nền tảng