Diyelim ki gece çalışan bir Delphi servisi müşteri başına bir XLSX üretiyor, birkaç yüz dosya, bazıları 400.000 satır genişliğinde. Profilini çıkardığınızda sürpriz nadiren hücre doldurma döngüsüdür. Sürpriz SaveAs çağrısıdır. Varsayılan yazıcıda her çalışma sayfası, OOXML zip içine sıkıştırılmadan önce bellekte tek bir XML dizgesine seri hale getirilir ve geniş bir sayfada bu geçici dizge, kendisini doğuran hücre modelini gölgede bırakabilir. Böylece verisini rahatça kuran ve 800 MB düzeyinde duran bir iş, kaydetme sırasında 2 GB kapsayıcı sınırını aşarak zirve yapar ve OOM killer hata raporunu gece 03.00'te, kimse bakmazken doldurur. losLab'ın Delphi ve C++Builder için yerel elektronik tablo kütüphanesi HotXLS, tam da bu zirveye nişan alan bir özellik taşır: StreamingWrite. Onun çevresinde, bir yığın işçisinin bellek ve zaman bütçesi içinde kalıp kalmayacağını belirleyen iki kaldıraç daha vardır: satır düzeyinde yazma geri çağırmaları ve stil havuzunun sıkı bir döngü içindeki davranışı
Varsayılan kaydetme yolu neyi tamponlar, StreamingWrite neyi değiştirir
Varsayılan XLSX yazıcısı basitliği yeğler. Çalışma sayfası XML'ini bütünüyle oluşturur, sonra bitmiş dizgeyi zip sıkıştırıcısına verir. Bütün sayfanın XML'inin birkaç megabayta sığdığı çalışma kitaplarının ezici çoğunluğu için bu doğru bir denge. Tek bir sayfanın seri hale getirilmiş biçimi yüzlerce megabayta ulaştığında doğru olmaktan çıkar. Elektronik tablo XML'i uzun soluklu bir biçimdir: her sayısal hücre onlarca karakterlik biçimlendirmeye mal olur ve hepsini tutan dizgenin bitişik olması gerekir. Bellek grafiğinde imzasını kaçırmak zordur. Satırlar dolarken uzun ve düz bir plato, ardından SaveAs sırasında keskin üçgen bir zirve, sonra zip boşaltılınca çöküş
Book.StreamingWrite := True ataması SaveAs çağrısını, sayfa XML'ini üretildikçe doğrudan zip akışına yazan bir çalışma sayfası yazıcısına geçirir. Ara dizge hiç ayrılmaz ve üçgen zirve gürültü seviyesine düzleşir
Bunun size gerçekte ne kazandırdığı konusunda kesin olun; çünkü abartmak yanlış kapasite planlarına yol açar. Bayrak yalnızca kaydetme yolunu değiştirir. Çalışma kitabını kurmak hâlâ bellekteki hücre modelinin tamamını ayırır; dolayısıyla doldurma evresindeki plato tam olarak eskisi kadar yüksektir. Ortadan kalkan şey, kaydetme anında o platonun üzerine binen seri hale getirme zirvesidir ve 400 bin satır dolduran bir iş için bu zirve, çoğu zaman bellek bütçesine sığmakla onu patlatmak arasındaki farkın tamamıdır. Özellik, tarihsel davranışı korumak için varsayılan olarak False gelir; yani devreye almak bilerek yazdığınız tek bir açık satırdır
Bayrak açıkken toplu bir dışa aktarım
Book := TXLSXWorkbook.Create;
try
BoldIdx := Book.Fonts.Add('Calibri', 11, True, False); // havuz dizini, 0 tabanlı
Sheet := Book.Sheets.Add('Bulk');
for R := 1 to 100000 do
begin
Sheet.Cells[R, 1].Value := R;
Sheet.Cells[R, 2].Value := 'Row ' + IntToStr(R);
Sheet.Cells[R, 3].Value := R * 1.5;
if (R mod 1000) = 0 then
Sheet.Cells[R, 2].FontIndex := BoldIdx + 1; // hücrede 1 tabanlı
end;
Book.StreamingWrite := True; // sayfa XML'ini doğrudan zip içine akıt
Book.SaveAs('bulk.xlsx');
finally
Book.Free;
end;
Cells[R, C] hücreleri istek üzerine oluşturur; bu da döngü gövdesini temiz tutar. Belleğe kazımaya değer iki ızgara sınırı vardır: XlsxMaxRow ve XlsxMaxCol olarak açılan 1.048.576 satır ve 16.384 sütun. Satır sınırını aşan bir veri akışının kendi kodunuzda sayfalara bölünmesi gerekir. Aşağı akışta hiçbir bileşen bu taşmayı fark etmez ya da sizin yerinize düzeltmez; dosya basitçe sınırda kırpılmış olarak kalır
Hücre başına Variant yükü olmadan satır doldurma
Her Cells[R, C].Value ataması bir hücre aramasının ve bir Variant dönüşümünün bedelini öder. On bin satırda kimse fark etmez. Her biri yirmi sütunluk bir milyon satırda ise bu çağrı başına yük, doldurma evresinin baskın maliyeti hâline gelir ve profil aracı doğrudan onu gösterir. Yığın arayüzleri bunun yerine yazıcıya bir kerede bütün bir satır vermenizi sağlar. WriteRows, her çağrıda bir satır sunan bir geri çağırmayı sürer:
procedure TBulkExporter.FillRow(Sender: TObject; SheetIndex, Row, FirstCol,
LastCol: Integer; var Values: Variant; var Skip: Boolean;
var Cancel: Boolean);
begin
if not FReader.Next then
begin
Cancel := True; // veri kaynağı tükendi: temiz biçimde dur
Exit;
end;
Values := VarArrayCreate([FirstCol, LastCol], varVariant);
Values[FirstCol] := FReader.RecordId;
Values[FirstCol + 1] := FReader.CustomerName;
Values[FirstCol + 2] := FReader.Amount;
end;
// 2..100001 satırlarını, A..C sütunlarını okuyucudan çekerek doldur
Sheet.WriteRows(2, 1, 100001, 3, FillRow);
Cancel bayrağı, sabit bir satır aralığını "en fazla N satır" hâline getiren şeydir; satır sayısı henüz çalıştırmayı bitirmediğiniz bir sorgudan geldiğinde doğal biçim budur. Skip daha hafif bir dokunuştur: çalıştırmayı durdurmadan tek bir satırı boş bırakır. Hücre doldurmanın ötesinde geri çağırma, aksi hâlde bir doldurma döngüsüne acemice cıvatalanan operasyonel kaygılar için iyi bir yuva olduğunu gösterir. Her bin satırda tıklayan bir ilerleme sayacı, iş zamanlayıcısından yoklanan bir iptal belirteci, kaynak veritabanından yapılan okumalara uygulanan bir hız sınırlayıcı: hepsi hücre yazma koduna serpiştirilmek yerine tek bir yerde yaşar. Okuma tarafında ForEachRow ve ForEachCell aynı deseni yansıtır; bu da hem büyük dosya tüketen hem de üreten bir yığın işi için önemlidir
Stil havuzları döngü dışına almayı ödüllendirir
XLSX biçimlendirme modeli bir paylaşılan havuzlar kümesidir. Fonts.Add, Fills.AddSolid ve Borders.Add hepsi 0 tabanlı bir havuz dizini döndürür; bir hücre ise bir yazı tipine, o dizinin bir fazlasını FontIndex içinde saklayarak başvurur ve sıfır çalışma kitabı varsayılanına ayrılmıştır. Yukarıdaki toplu örnekte bu +1 tam oradadır. Unutursanız hücre sessizce yanlış stili kapar; çünkü bir stil havuzu dizinindeki bir birim kayma yine geçerli bir dizindir ve hiçbir şey hata fırlatmaz
Bundan çıkan disiplin, her stil nesnesini satır döngüsünden önce oluşturmak ve döngü içinde yalnızca dizinine başvurmaktır. Fonts.Add özdeş tanımları tekilleştirir; dolayısıyla satır başına bir kez çağırmak yalnızca CPU harcar. Asıl tuzak Alignments.Add çağrısıdır; çünkü her çağrıda yeni bir kayıt döndürür. 100 bin satırlık bir döngü içinde bu, styles.xml dosyasını yüz bin yinelenen hizalama kaydının altına gömer; bu da diskteki dosyayı şişirir ve yinelenenler yeniden ayrıştırıldıkça Excel'deki her sonraki açılışı yavaşlatır. Her stili döngü dışında bir kez kurun, sonra dizinine ihtiyacınız kadar başvurun
Akışlar, geçici dizinler ve hepsini saran yığın döngüsü
Bunların hiçbiri bir dosya sistemi gerektirmez. Her iki cephe de IO yüzeyi boyunca TStream aşırı yüklemeleri taşır; aralarında Open, SaveAs, SaveAsCSV, SaveAsHTML ve SaveAsODS vardır. Böylece bir yığın işçisi, diske hiç dokunmadan doğrudan blob depolamaya ya da bir HTTP yanıtına gidecek bir TMemoryStream içine üretim yapabilir. Akılda tutulması gereken keskin bir kenar var. SaveAs(Stream) akışın geçerli konumundan itibaren yazar ve sonrasında geri sarmaz; bu yüzden akışı teslim edecek olan her neyse ona vermeden önce Position := 0 ayarını kendiniz yapın, yoksa tüketici sıfır bayt okur. XLS cephesi kendine ait iki düğme ekler. SetTempDir, BIFF yazıcısının geçici dosyalarını onları soğuracak alana ve IO payına sahip bir birime yönlendirir; varsayılan geçici yolun dar bir sistem diskinde durduğu sunucularda bu önem taşır. UseSharedFormulas ise yinelenen formül gövdelerini paylaşılan gruplara katlar; tek bir formülün bütün bir sütun boyunca kopyalandığı klasik rapor biçiminde bu gerçek bir boyut düşüşüdür
Yığın döngüsünün kendisi bilerek sıkıcı kalır:
for FileName in SourceFiles do
begin
Book := TXLSXWorkbook.Create; // taze örnek: durum sızıntısı yok
try
Book.StreamingWrite := True;
if Book.Open(FileName) <> 1 then
Continue; // tek bir bozuk girdi yığını öldürmemeli
Book.SaveAsCSV(ChangeFileExt(FileName, '.csv'), 0, ',');
finally
Book.Free;
end;
end;
Dosya başına taze bir çalışma kitabı örneği mikrosaniyelere mal olur ve bütün bir dosyalar arası bulaşma hatası sınıfını ortadan kaldırır: 17 numaralı dosyanın stilleri, tanımlı adları ve belge özellikleri 18 numaralı dosyaya sızacak bir yol bulamaz. Başarısız bir Open çağrısında atlayıp devam etmek de en az onun kadar yerini hak eder; çünkü 600 dosyalık bir yığındaki tek bir kırpılmış yükleme size çalıştırmanın geri kalanına değil, tek bir günlük satırına mal olmalıdır. CSV ayağının bilerek yapmadığı şeye de dikkat çekmeye değer. SaveAsCSV formülleri düz metin olarak yazar ve hiçbir zaman değerlendirmez; dolayısıyla tüketicileri hesaplanmış sayılar bekleyen bir dönüştürme yığınının önce ilgili hücrelerde Calculate çalıştırması ya da önceki bir hesaplamadan gelen önbelleklenmiş sonuçları zaten taşıyan çalışma kitaplarından başlaması gerekir
Eşzamanlılık modeli: iş parçacığı başına bir çalışma kitabı
Hiçbir cephenin nesneleri iş parçacığı güvenli değildir ve tasarım hiçbir zaman öyleymiş gibi davranmadı. Örnekler arasında paylaşılan genel durum bulunmadığı için ölçekleme kuralı basitçe işçi iş parçacığı başına bir çalışma kitabıdır ve bir çalışma kitabı iş parçacıkları arasında paylaşılmaz. Her biri kendi TXLSXWorkbook nesnesine sahip N işçiden oluşan bir havuz, bellek tavan olana kadar neredeyse doğrusal ölçeklenir ve bu tavana bir sayı koyabilirsiniz: eşzamanlı en büyük hücre modelinin işçi sayısıyla çarpımı, artı StreamingWrite ile düzleştirilmiş kaydetme anı yükü ne kadarsa o. Kuyruk derinleştiğinde geri basıncı yazıcının içinde değil iş kuyruğunda uygulayın. Bir çalışma kitabını yarı yazmışken aç kalan bir iş parçacığı işe yarar hiçbir şey üretmemiştir; oysa boş bir işçi için birkaç saniye bekleyen bir iş eksiksiz tamamlanır
Paylaşılan formüller, okuma tarafında grafik atlama ve XLS'e özgü kaldıraçlar dahil daha geniş ayar tablosu için büyük çalışma kitabı başarım rehberine bakın. Satırları doğrudan bir sorgudan gelen yığın işleri ise ayrıca Delphi raporları için veritabanı dışa aktarma desenlerinde ele alınıyor
HotXLS, Delphi veya C++Builder servisinize hiçbir dış bağımlılığı olmayan yerel Object Pascal olarak derlenir; sürümler ve lisanslama HotXLS Delphi Component ürün sayfasındadır