Enumerable: Cara menghasilkan nilai bisnis

Artikel ini adalah penjelasan singkat tentang bagaimana menggunakan kata kunci bahasa umum mungkin memiliki pengaruh pada anggaran infrastruktur TI suatu proyek atau membantu mencapai beberapa batasan / batasan infrastruktur hosting dan, terlebih lagi, akan menjadi ciri kualitas yang baik. dan kematangan kode sumber.

Untuk demonstrasi ide, dalam artikel akan menggunakan bahasa C #, tetapi sebagian besar ide dapat diterjemahkan ke bahasa lain.

Dari serangkaian fitur bahasa, dari sudut pandang saya, 'hasil' adalah kata kunci yang paling undervalued. Anda dapat membaca dokumentasi dan menemukan banyak contoh di Internet. Singkatnya, katakanlah 'menghasilkan' memungkinkan membuat 'iterator' secara implisit. Sesuai desain, iterator harus mengekspos sumber IEnumerable untuk penggunaan 'publik'. Dan di sini mulailah hal yang sulit. Karena kami memiliki banyak implementasi IEnumerable dalam bahasa: daftar, kamus, hashset, antrian, dll. Dan dari pengalaman saya, memilih salah satunya untuk persyaratan kepuasan dari beberapa tugas bisnis adalah salah. Terlebih lagi, semua ini diperparah dengan implementasi apa pun yang dipilih, program 'hanya berfungsi' - inilah yang benar-benar dibutuhkan untuk bisnis, bukan? Secara umum, ini berfungsi, tetapi hanya sampai layanan dikerahkan ke lingkungan produksi.

Untuk demonstrasi masalah, saya sarankan memilih kasus bisnis / aliran yang sangat umum untuk sebagian besar proyek perusahaan yang dapat kami sampaikan selama artikel dan mengganti beberapa bagian dari aliran ini untuk memahami skala pengaruh pendekatan ini pada proyek perusahaan. Dan itu akan membantu Anda menemukan kasing Anda sendiri di set ini untuk memperbaikinya.

Contoh tugas:

  1. Memuat byline satu set catatan dari file atau DB ke dalam memori.
  2. Untuk setiap kolom catatan, ubah nilainya ke nilai orang lain.
  3. Simpan hasil transformasi menjadi file atau DB.

Mari kita asumsikan beberapa kasus di mana logika ini dapat diterapkan. Saat ini, saya melihat dua kasus:

  1. Ini mungkin bagian dari aliran untuk beberapa aplikasi konsol ETL.
  2. Ini mungkin merupakan logika tindakan dalam Pengontrol aplikasi MVC.

Jika kita memparafrasekan tugas menjadi lebih teknis, maka mungkin terdengar seperti ini: "(1) Alokasikan sejumlah memori, (2) muat informasi ke dalam memori dari penyimpanan persisten, (3) modifikasi dan (4) catatan flush perubahan memori ke penyimpanan ketekunan. " Di sini frase pertama dalam deskripsi "(1) Alokasikan jumlah memori" mungkin memiliki korelasi nyata dengan persyaratan non-fungsional Anda. Karena pekerjaan / layanan Anda harus 'hidup' di beberapa lingkungan hosting yang mungkin memiliki beberapa batasan / batasan (misalnya, 150 MB per layanan mikro) dan untuk memprediksi pengeluaran untuk layanan Anda dalam anggaran, kami harus memprediksi, dalam kasus kami jumlah memori layanan mana yang akan digunakan (umumnya kita katakan tentang jumlah memori maksimum). Dengan kata lain, kami harus menentukan 'jejak kaki' memori untuk layanan Anda.

Mari kita pertimbangkan jejak memori untuk implementasi yang benar-benar umum yang saya amati dari waktu ke waktu dalam basis kode proyek perusahaan yang berbeda. Juga, Anda dapat mencoba menemukannya di proyek Anda juga, misalnya, 'di bawah kap' dari implementasi pola 'repositori', coba saja menemukan kata-kata seperti: 'ToList', 'ToArray', 'ToReadonlyCollection' dan lain-lain. Semua implementasi tersebut berarti:

1. Untuk setiap baris / rekam ke file / db, alokasikan memori untuk menyimpan properti catatan dari file / db (yaitu var user = Pengguna baru () {FirstName = 'Test', LastName = 'Test2'})

2. Selanjutnya, dengan bantuan, misalnya, 'ToArray' atau secara manual, referensi objek disimpan ke dalam beberapa koleksi (yaitu var users = new list (); users.Add (user)). Jadi, itu dialokasikan sejumlah memori untuk setiap catatan dari file dan tidak melupakannya, referensi disimpan ke dalam beberapa koleksi.

Berikut ini sebuah contoh:

private static IEnumerable<User> LoadUsers2() { var list = new List<User>(); foreach(var line in File.ReadLines("text.txt")) { var splittedLine = line.Split(';'); list.Add(new User() { FirstName = splittedLine[0], LastName = splittedLine[1] }); } return list; // or return File.ReadLines("text.txt") .Select(line => line.Split(';')) .Select(splittedLine => new User() { FirstName = splittedLine[0], LastName = splittedLine[1] }).ToArray(); } 

Hasil memori profiler:

gambar

Gambaran persis seperti itu yang saya lihat setiap kali di lingkungan produksi sebelum kontainer berhenti / dimuat ulang karena keterbatasan sumber daya hosting per kontainer.

Jadi, jejak untuk kasus ini, secara kasar, tergantung pada jumlah rekaman ke dalam file. Karena memori mengalokasikan per catatan dalam file. Dan, jumlah memori kecil ini memberi kami jumlah memori maksimum yang dapat dikonsumsi oleh layanan kami - ini adalah jejak layanan. Tetapi apakah jejak ini dapat diprediksi? Rupanya tidak. Karena kami tidak dapat memprediksi sejumlah catatan dalam file. Dan, dalam banyak kasus, ukuran file melebihi jumlah memori yang diizinkan dalam hosting beberapa kali. Ini berarti sulit untuk menggunakan implementasi seperti itu di lingkungan produksi.

Sepertinya inilah saatnya untuk memikirkan kembali implementasi tersebut. Asumsi berikutnya dapat memberi kita lebih banyak peluang untuk menghitung jejak untuk layanan: "jejak harus bergantung pada ukuran hanya SATU catatan dalam file". Secara kasar, dalam hal ini, kita dapat menghitung ukuran maksimum dari setiap kolom hanya satu catatan dan menjumlahkannya. Sangat mudah untuk memprediksi ukuran catatan alih-alih prediksi jumlah catatan dalam file.

Dan sungguh mengherankan bahwa kita dapat mengimplementasikan layanan yang dapat menangani jumlah rekaman yang tidak dapat diprediksi dan terus-menerus hanya mengonsumsi beberapa megabita dengan bantuan hanya satu kata kunci - 'hasil' *.

Waktu untuk contoh:

 class Program { static void Main(string[] args) { // 1. Load byline a set of records from a file or DB into memory. var users = LoadUsers(); // 2. For each column of the record change the value to someone other value. users = ModifyFirstName(users); // 3. Save the results of transformation into a file or DB. SaveUsers(users); } private static IEnumerable<User> LoadUsers() { foreach(var line in File.ReadLines("text.txt")) { var splitedLine = line.Split(';'); yield return new User() { FirstName = splitedLine[0], LastName = splitedLine[1] }; } } private static IEnumerable<User> ModifyFirstName(IEnumerable<User> users) { foreach (var user in users) { user.FirstName += "_1"; yield return user; } } private static void SaveUsers(IEnumerable<User> users) { foreach(var user in users) { File.AppendAllLines("results.txt", new string []{ user.FirstName + ';' + user.LastName }); } } private class User { public string FirstName { get; set; } public string LastName { get; set; } } } 

Seperti yang Anda lihat dalam contoh di atas, ada alokasi memori hanya untuk satu objek pada satu waktu: 'menghasilkan kembali Pengguna baru ()' alih-alih membuat koleksi dan mengisinya dengan objek. Ini adalah titik utama optimasi yang memungkinkan kami menghitung jejak memori yang lebih mudah diprediksi untuk layanan ini. Karena kita hanya perlu mengetahui ukuran dua bidang, dalam kasus kami FirstName dan LastName. Ketika pengguna yang dimodifikasi disimpan ke dalam file (lihat File.AppendAllLines), instance objek pengguna tersedia untuk pengumpulan sampah. Dan memori yang ditempati oleh objek tersebut tidak dapat dialokasikan (mis. Iterasi berikutnya dari pernyataan 'foreach' di LoadUsers), sehingga instance objek pengguna berikutnya dapat dibuat. Dengan kata lain, secara kasar, jumlah memori yang sama menggantikan dengan jumlah memori yang sama pada setiap iterasi. Itu sebabnya kita tidak perlu lebih banyak memori daripada ukuran satu catatan dalam file.

Hasil memori profiler setelah optimasi:

gambar

Dari perspektif lain, jika kita sedikit mengubah nama beberapa metode dalam implementasi di atas, sehingga penggunaan dapat melihat beberapa logika yang bermakna untuk Pengontrol dalam aplikasi MVC:

 private static void GetUsersAction() { // 1. Load byline a set of records from a file or DB into memory. var users = LoadUsers(); // 2. For each column of the record change the value to someone other value. var usersDTOs = MapToDTO(users); // 3. Save the results of transformation into a file or DB. OkResult(usersDTOs); } 

Satu catatan penting sebelum daftar kode: sebagian besar perpustakaan penting seperti EntityFramework, ASP.net MVC, AutoMapper, Dapper, NHibernate, ADO.net dan lain-lain memaparkan / menggunakan sumber IEnumerables. Jadi, itu berarti dalam contoh di atas bahwa LoadUsers dapat diganti dengan implementasi yang menggunakan EntityFramework, misalnya. Yang memuat data baris demi baris dari tabel DB, bukan file. MapToDTO dapat digantikan oleh Automapper dan OkResult dapat digantikan oleh implementasi IActionResult 'nyata' dalam beberapa kerangka kerja MVC atau basis implementasi kami sendiri pada aliran jaringan, misalnya:

 private static void OkResult(IEnumerable<User> users) { // you can use a networksteam implementation using(StreamWriter sw = new StreamWriter("result.txt")) { foreach(var user in users) { sw.WriteLine(user.FirstName + ';' + user.LastName); } } } 

Contoh 'mirip-pvc' ini menunjukkan kepada kami bahwa kami masih dapat memprediksi dan menghitung jejak memori juga untuk aplikasi Web. Tetapi dalam hal ini, itu akan tergantung pada permintaan juga. Misalnya, persyaratan non-fungsional mungkin terdengar seperti ini: "Jumlah memori maksimum untuk 1000 permintaan tidak lebih dari: 200KB per objek pengguna x 1000 permintaan ~ 200MB."

Perhitungan semacam itu sangat berguna untuk pengoptimalan kinerja jika melakukan penskalaan aplikasi web. Misalnya, Anda perlu mengatur skala aplikasi web Anda pada 100 wadah / VM. Jadi, dalam hal ini, untuk membuat keputusan tentang berapa banyak sumber daya yang harus Anda alokasikan dari penyedia hosting, sehingga Anda dapat menyesuaikan rumus seperti ini: 200KB per objek pengguna x 1000 permintaan x 100VMs ~ 20GB. Selain itu, ini adalah jumlah maksimum memori dan jumlah ini di bawah kendali anggaran proyek Anda.

Saya berharap bahwa informasi dari artikel ini akan membantu dan memungkinkan untuk menghemat banyak uang dan waktu dalam proyek Anda.

Source: https://habr.com/ru/post/id444358/


All Articles