Her gece çalışan bir Delphi servisinin müşteri başına birer adet XLSX dosyası ürettiğini varsayalım; birkaç yüz dosya ve bazısı 400.000 satır genişliğinde. Bunu analiz ettiğinizde (profile) sürpriz nadiren hücre doldurma döngüsündedir. Genellikle SaveAs çağrısındadır. Varsayılan yazıcı ile, her çalışma sayfası, o dize OOXML zip dosyasına sıkıştırılmadan önce tek bir bellek içi XML dizesine serileştirilir ve geniş bir sayfa için geçici dize, üzerine kurulduğu hücre modelini gölgede bırakabilir. Dolayısıyla verilerini rahatça oluşturan ve 800 MB'ta oturan bir iş, kaydetme sırasında 2 GB'lık kapsayıcı sınırını aşacak ve kimse izlemezken saat 03:00'te OOM (bellek yetersiz) hatasıyla çökebilecektir. losLab'in Delphi ve C++Builder için yerel elektronik tablo kütüphanesi olan HotXLS, doğrudan bu sıçramayı hedefleyen bir özelliğe sahiptir: StreamingWrite. Bunun etrafında, toplu bir işçinin bellek ve zaman bütçesi içinde kalıp kalmayacağını belirleyen iki manivela daha bulunur: satır düzeyinde yazma geri çağırmaları (write callbacks) ve stil havuzunun sıkı bir döngü içindeki davranış biçimi
Varsayılan kaydetme yolunun neleri arabelleğe aldığı ve StreamingWrite'ın neleri değiştirdiği
Varsayılan XLSX yazıcısı basitliği tercih eder. Çalışma sayfası XML'ini tamamen oluşturur, ardından bitmiş dizeyi zip sıkıştırıcısına teslim eder. Bu, sayfanın XML'inin tamamının birkaç megabayta sığdığı çalışma kitaplarının ezici çoğunluğu için doğru ticarettir. Bir sayfanın serileştirilmiş biçimi yüzlerce megabayta ulaştığında ise bu durum doğru olmaktan çıkar. Elektronik tablo XML'i ayrıntılıdır: her sayısal hücre onlarca karakter biçimlendirme işaretine mal olur ve tüm bunları tutan dizenin bitişik (contiguous) olması gerekir. Bir bellek grafiğinde bu imza gözden kaçırılamaz: Satırlar dolarken uzun, düz bir plato, ardından SaveAs sırasında keskin bir üçgen sıçrama ve zip boşaltıldığında çöküş
Book.StreamingWrite := True ayarını yapmak, SaveAs işlevini, oluşturulduğu anda sayfa XML'ini doğrudan zip akışına aktaran bir çalışma sayfası yazıcısına geçirir. Ara dize hiçbir zaman tahsis edilmez (allocate) ve üçgen sıçrama gürültüye dönüşür
Bunun size gerçekte ne kazandırdığı konusunda kesin olun, çünkü bunu abartmak yanlış kapasite planlarına yol açar. Bu bayrak yalnızca kaydetme yolunu değiştirir. Çalışma kitabını oluşturmak hala bellek içi hücre modelinin tamamını tahsis eder, dolayısıyla doldurma aşamasındaki plato öncekiyle tamamen aynı yüksekliktedir. Ortadan kalkan şey, kaydetme sırasında o platonun üzerine binen serileştirme sıçramasıdır ve 400k satır dolduran bir iş için bu sıçrama, genellikle bellek bütçesine sığmak ile onu aşmak arasındaki tüm farkı oluşturur. Özellik, geçmiş davranışı korumak için varsayılan olarak False değerini alır, bu nedenle dahil olmak kasıtlı olarak yazdığınız açık bir satırdır
Bayrak etkinken toplu dışa aktarma
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 aktarır
Book.SaveAs('bulk.xlsx');
Cells[R, C] talep üzerine hücreler oluşturur, bu da döngü gövdesini temiz tutar. Akılda tutulması gereken iki tablo sınırı vardır: XlsxMaxRow ve XlsxMaxCol olarak sunulan 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 sayfalar arasında bölünmesi gerekir. Aşağı akıştaki hiçbir şey aşımı fark etmez veya sizin yerinize düzeltmez ve dosya sınırda kesilmiş olarak kalır
Hücre başına Variant yükü olmadan satırları doldurma
Her hücre değeri ataması, bir hücre araması ve bir Variant dönüşümü maliyeti getirir. On bin satırda bunu kimse fark etmez. Her biri yirmi sütundan oluşan bir milyon satırda, çağrı başına gelen bu yük doldurma aşamasının baskın maliyeti haline gelir ve analizör (profiler) doğrudan bunu gösterecektir. Toplu arayüzler, bunun yerine yazıcıya tek seferde bütün bir satırı vermenize olanak tanır. WriteRows, çağrı başına bir satır sağlayan bir geri çağırmayı (callback) yönlendirir:
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: temizce 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ı doldurur, okuyucudan çeker
Sheet.WriteRows(2, 1, 100001, 3, FillRow);
Cancel bayrağı, sabit bir satır aralığını "N satıra kadar" haline getiren şeydir; bu, satır sayısı henüz yürütülmesini bitirmediğiniz bir sorgudan geldiğinde doğal şekildir. Skip daha hafif bir dokunuştur: çalışmayı durdurmadan tek bir satırı boş bırakır. Hücreleri doldurmanın ötesinde, geri çağırma, aksi takdirde bir doldurma döngüsüne garip şekillerde cıvatalanacak operasyonel endişeler için iyi bir yuva haline gelir. Her bin satırda bir tik atan bir ilerleme sayacı, iş zamanlayıcıdan sorgulanan bir iptal belirteci, kaynak veri tabanından okumalarda bir hız sınırlayıcı: bunların tümü hücre yazma koduna işlenmek yerine tek bir yerde yaşar. Okuma tarafında, ForEachRow ve ForEachCell aynı kalıbı yansıtır, bu da toplu bir işin büyük dosyaları hem tükettiği hem de ürettiği durumlarda önem taşır
Stil havuzları yukarı taşımayı ödüllendirir
XLSX stil modeli paylaşılan havuzlardan oluşur. Fonts.Add, Fills.AddSolid ve Borders.Add yöntemlerinin tümü 0 tabanlı bir havuz dizini döndürür ve bir hücre, sıfırın çalışma kitabı varsayılanı için ayrıldığı FontIndex içinde bu dizin artı bir değerini saklayarak bir yazı tipine başvurur. +1 değeri yukarıdaki toplu örnekte tam oradadır. Bunu unutursanız hücre sessizce yanlış stili alır çünkü stil havuzu dizinindeki bir eksik/fazla hata hala geçerli bir dizindir ve hiçbir şey hata fırlatmaz
Bunu takip eden disiplin, her stil nesnesini satır döngüsünden önce oluşturmak ve döngü içinde dizinine başvurmaktır. Fonts.Add aynı tanımları tekilleştirir, bu nedenle satır başına bir kez çağırmak yalnızca CPU'yu boşa harcar. Alignments.Add ise bir tuzaktır çünkü her çağrıda yeni bir girdi döndürür. 100k satırlık bir döngü içinde bu, styles.xml dosyasını yüz bin yinelenen hizalama kaydı altında gömer, bu da diskteki dosyayı şişirir ve kopyalar yeniden ayrıştırılırken Excel'deki her sonraki açılışı yavaşlatır. Her stili döngü dışında bir kez oluşturun, ardından ihtiyacınız olduğu kadar çok kez dizinine başvurun
Akışlar, geçici dizinler ve tüm bunların etrafındaki toplu döngü
Bunların hiçbiri bir dosya sistemi gerektirmez. Her iki arayüz de IO yüzeylerinde TStream aşırı yüklemelerini taşır; Open, SaveAs, SaveAsCSV, SaveAsHTML ve SaveAsODS bunlardan bazılarıdır, böylece toplu bir işçi diske hiç dokunmadan doğrudan bulut depolama alanı veya bir HTTP yanıtı için tasarlanmış bir TMemoryStream içine yazabilir. Hatırlanmesi gereken keskin bir nokta vardır. SaveAs(Stream) akışın geçerli konumundan yazar ve sonrasında geri sarmaz, bu nedenle akışı teslim eden her neyse ona vermeden önce Position := 0 ayarını kendiniz yapın, aksi takdirde tüketici sıfır bayt okur. XLS arayüzü kendine ait iki düğme ekler. SetTempDir, BIFF yazıcısının geçici dosyalarını bunları alacak alana ve IO genişliğine sahip bir birime yönlendirir; bu, varsayılan geçici yolun sıkışık bir sistem diskinde bulunduğu sunucularda önem taşır. UseSharedFormulas, yinelenen formül gövdelerini paylaşılan gruplara katlar; bu, bir formülün tüm sütun boyunca kopyalandığı klasik rapor şekli için gerçek bir boyut küçültmedir
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; // bir kötü girdi tüm grubu öldürmemeli
Book.SaveAsCSV(ChangeFileExt(FileName, '.csv'), 0, ',');
finally
Book.Free;
end;
end;
Dosya başına yeni bir çalışma kitabı örneği oluşturmak mikrosaniyelere mal olur ve tüm bir dosyalar arası bulaşma hatası kategorisini ortadan kaldırır: dosya 17'deki stillerin, tanımlanmış adların ve belge özelliklerinin dosya 18'e sızacak bir yolu kalmaz. Başarısız bir Open işleminde atla-ve-devam-et yöntemi de aynı derecede değerini kanıtlar çünkü 600 dosyadan oluşan bir gruptaki tek bir kesintili yükleme, çalışmanın geri kalanı yerine size tek bir günlük satırına mal olmalıdır. CSV bacağının kasıtlı olarak neyi yapmadığını da belirtmekte fayda var. SaveAsCSV formülleri düz metin olarak yazar ve onları asla değerlendirmez, bu nedenle tüketicileri hesaplanmış sayılar bekleyen bir dönüştürme grubunun önce ilgili hücrelerde Calculate çalıştırması veya zaten önceki bir hesaplamadan önbelleğe alınmış sonuçları taşıyan çalışma kitaplarından başlaması gerekir
Eşzamanlılık modeli: İş parçacığı (thread) başına bir çalışma kitabı
Hiçbir arayüzün nesneleri thread-safe (iş parçacığı açısından güvenli) değildir ve tasarım hiçbir zaman aksini iddia etmemiştir. Örnekler arasında paylaşılan küresel bir durum olmadığından, ölçeklendirme kuralı iş parçacıkları arasında bir çalışma kitabının paylaşılmaması koşuluyla iş parçacığı başına tek bir çalışma kitabıdır. Her biri kendi TXLSXWorkbook nesnesine sahip N adet işçiden oluşan bir havuz, bellek tavan olana kadar neredeyse doğrusal olarak ölçeklenir ve bu tavan bir sayı ile ifade edilebilir: en büyük eşzamanlı hücre modelinin işçi sayısı ile çarpımı artı StreamingWrite'ın düzleştirdiği kaydetme süresi yükü. Kuyruk derinleştiğinde, yazıcının içi yerine iş kuyruğuna geri basınç (back-pressure) uygulayın. Bir çalışma kitabını yarım yazmış aç bir iş parçacığı yararlı hiçbir şey üretmemişken, boş bir işçi için birkaç saniye bekleyen bir iş sağlam bir şekilde tamamlanır
Paylaşılan formüller, okuma tarafında grafikleri atlama ve XLS'ye özgü manivelalar dahil olmak üzere daha geniş optimizasyon resmi için büyük çalışma kitabı performans kılavuzuna bakın. Satırları doğrudan bir sorgudan gelen toplu işler, Delphi raporları için veri tabanı dışa aktarım kalıplarında ayrı olarak ele alınmıştır
HotXLS, Delphi veya C++Builder servisinize hiçbir harici bağımlılığı olmadan yerel Object Pascal olarak derlenir; sürümler ve lisanslama HotXLS Bileşeni ürün sayfasında yer almaktadır