एक में पिछले प्रश्न एक प्रारूपित करने के बारे double[][]
सीएसवी प्रारूप करने के लिए, यह सुझाव दिया गया था कि का उपयोग करते हुए StringBuilder
तेजी से होगा String.Join
। क्या ये सच है?
एक में पिछले प्रश्न एक प्रारूपित करने के बारे double[][]
सीएसवी प्रारूप करने के लिए, यह सुझाव दिया गया था कि का उपयोग करते हुए StringBuilder
तेजी से होगा String.Join
। क्या ये सच है?
जवाबों:
संक्षिप्त उत्तर: यह निर्भर करता है।
लंबा उत्तर: यदि आपके पास पहले से ही एक साथ (एक सीमांकक के साथ) समतल करने की एक सरणी है, तो String.Join
यह करने का सबसे तेज़ तरीका है।
String.Join
सभी स्ट्रिंग्स के माध्यम से देख सकते हैं कि इसकी सही लंबाई की आवश्यकता है, फिर से जाएं और सभी डेटा को कॉपी करें। इसका मतलब है कि इसमें कोई अतिरिक्त नकल नहीं होगी । केवल नकारात्मक पक्ष यह है कि यह तार के माध्यम से दो बार जाने के लिए है, जो साधन संभावित मेमोरी कैश आवश्यकता से अधिक बार बह गई है।
आप तो नहीं है पहले से एक सरणी के रूप में तार है, यह शायद तेजी से उपयोग करने के लिए StringBuilder
- लेकिन स्थितियों होगा जहां यह नहीं है। यदि StringBuilder
बहुत सारी और बहुत सारी प्रतियों का उपयोग करने वाले साधनों का उपयोग किया जाता है, तो एक सरणी का निर्माण करना और फिर कॉल String.Join
करना अच्छी तरह से तेज हो सकता है।
EDIT: यह कॉल करने के लिए कॉल का String.Join
एक गुच्छा बनाम एकल कॉल के संदर्भ में है StringBuilder.Append
। मूल प्रश्न में, हमारे पास String.Join
कॉल के दो अलग-अलग स्तर थे , इसलिए प्रत्येक नेस्टेड कॉल ने एक मध्यवर्ती स्ट्रिंग बनाई होगी। दूसरे शब्दों में, इसके बारे में अनुमान लगाना और भी जटिल और कठिन है। मैं विशिष्ट डेटा के साथ किसी भी तरह से "जीत" काफी (जटिलता शब्दों में) देखकर आश्चर्यचकित रहूंगा।
संपादित करें: जब मैं घर पर होता हूं, तो मैं एक बेंचमार्क लिखूंगा जो संभवतः के लिए उतना ही दर्दनाक है StringBuilder
। मूल रूप से यदि आपके पास एक ऐसा सरणी है जहां प्रत्येक तत्व पिछले एक के आकार से लगभग दोगुना है, और आपको यह सही लगता है, तो आपको प्रत्येक परिशिष्ट (तत्वों के नहीं, परिसीमाक की नहीं) के लिए एक प्रतिलिपि तैयार करने में सक्षम होना चाहिए, हालांकि इसकी आवश्यकता है इस पर भी ध्यान दिया जाए)। उस बिंदु पर यह लगभग उतना ही बुरा है जितना कि साधारण स्ट्रिंग का संघनन - लेकिन String.Join
इसमें कोई समस्या नहीं होगी।
StringBuilder
एक मूल स्ट्रिंग के साथ निर्माण करना , फिर Append
एक बार कॉल करना ? हां, मैं string.Join
वहां जीतने की उम्मीद करूंगा ।
string.Join
उपयोगों का कार्यान्वयन StringBuilder
।
यहाँ मेरी परीक्षण रिग, int[][]
सादगी के लिए उपयोग कर रहा है; पहले परिणाम:
Join: 9420ms (chk: 210710000
OneBuilder: 9021ms (chk: 210710000
( double
परिणामों के लिए अपडेट :)
Join: 11635ms (chk: 210710000
OneBuilder: 11385ms (chk: 210710000
(अपडेट २०४ 20 * ६४ * १५०)
Join: 11620ms (chk: 206409600
OneBuilder: 11132ms (chk: 206409600
और OptimizeForTesting सक्षम के साथ:
Join: 11180ms (chk: 206409600
OneBuilder: 10784ms (chk: 206409600
इतनी तेजी से, लेकिन बड़े पैमाने पर ऐसा नहीं है; रिग (कंसोल पर, रिलीज़ मोड में, आदि):
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
namespace ConsoleApplication2
{
class Program
{
static void Collect()
{
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
GC.WaitForPendingFinalizers();
}
static void Main(string[] args)
{
const int ROWS = 500, COLS = 20, LOOPS = 2000;
int[][] data = new int[ROWS][];
Random rand = new Random(123456);
for (int row = 0; row < ROWS; row++)
{
int[] cells = new int[COLS];
for (int col = 0; col < COLS; col++)
{
cells[col] = rand.Next();
}
data[row] = cells;
}
Collect();
int chksum = 0;
Stopwatch watch = Stopwatch.StartNew();
for (int i = 0; i < LOOPS; i++)
{
chksum += Join(data).Length;
}
watch.Stop();
Console.WriteLine("Join: {0}ms (chk: {1}", watch.ElapsedMilliseconds, chksum);
Collect();
chksum = 0;
watch = Stopwatch.StartNew();
for (int i = 0; i < LOOPS; i++)
{
chksum += OneBuilder(data).Length;
}
watch.Stop();
Console.WriteLine("OneBuilder: {0}ms (chk: {1}", watch.ElapsedMilliseconds, chksum);
Console.WriteLine("done");
Console.ReadLine();
}
public static string Join(int[][] array)
{
return String.Join(Environment.NewLine,
Array.ConvertAll(array,
row => String.Join(",",
Array.ConvertAll(row, x => x.ToString()))));
}
public static string OneBuilder(IEnumerable<int[]> source)
{
StringBuilder sb = new StringBuilder();
bool firstRow = true;
foreach (var row in source)
{
if (firstRow)
{
firstRow = false;
}
else
{
sb.AppendLine();
}
if (row.Length > 0)
{
sb.Append(row[0]);
for (int i = 1; i < row.Length; i++)
{
sb.Append(',').Append(row[i]);
}
}
}
return sb.ToString();
}
}
}
OptimizeForTesting()
विधि का उपयोग करते हैं, तो भी आपके परिणाम किसी भी तरह से भिन्न होंगे ?
मुझे ऐसा नहीं लगता। परावर्तक के माध्यम से देखते हुए, String.Join
बहुत ही अनुकूलित लग रहा है के कार्यान्वयन । इसमें अग्रिम रूप से बनाए जाने वाले स्ट्रिंग के कुल आकार को जानने का अतिरिक्त लाभ भी है, इसलिए इसे किसी भी प्राप्ति की आवश्यकता नहीं है।
मैंने उनकी तुलना करने के लिए दो परीक्षण विधियाँ बनाई हैं:
public static string TestStringJoin(double[][] array)
{
return String.Join(Environment.NewLine,
Array.ConvertAll(array,
row => String.Join(",",
Array.ConvertAll(row, x => x.ToString()))));
}
public static string TestStringBuilder(double[][] source)
{
// based on Marc Gravell's code
StringBuilder sb = new StringBuilder();
foreach (var row in source)
{
if (row.Length > 0)
{
sb.Append(row[0]);
for (int i = 1; i < row.Length; i++)
{
sb.Append(',').Append(row[i]);
}
}
}
return sb.ToString();
}
मैंने प्रत्येक विधि को 50 बार चलाया, आकार के एक सरणी में गुजर रहा है [2048][64]
। मैंने दो सरणियों के लिए ऐसा किया; एक शून्य से भरा है और दूसरा यादृच्छिक मूल्यों से भरा है। मुझे अपनी मशीन (P4 3.0 GHz, सिंगल-कोर, कोई HT, CMD से रिलीज़ मोड चल रहा है) पर निम्नलिखित परिणाम मिले:
// with zeros:
TestStringJoin took 00:00:02.2755280
TestStringBuilder took 00:00:02.3536041
// with random values:
TestStringJoin took 00:00:05.6412147
TestStringBuilder took 00:00:05.8394650
सरणी का आकार [2048][512]
बढ़ाते हुए, पुनरावृत्तियों की संख्या घटाकर 10 करने से मुझे निम्नलिखित परिणाम मिले:
// with zeros:
TestStringJoin took 00:00:03.7146628
TestStringBuilder took 00:00:03.8886978
// with random values:
TestStringJoin took 00:00:09.4991765
TestStringBuilder took 00:00:09.3033365
परिणाम दोहराए जाने योग्य हैं (लगभग, विभिन्न यादृच्छिक मूल्यों के कारण छोटे उतार-चढ़ाव के साथ)। स्पष्ट रूप String.Join
से अधिकांश समय थोड़ा तेज होता है (हालांकि बहुत कम मार्जिन से)।
यह वह कोड है जिसे मैंने परीक्षण के लिए उपयोग किया है:
const int Iterations = 50;
const int Rows = 2048;
const int Cols = 64; // 512
static void Main()
{
OptimizeForTesting(); // set process priority to RealTime
// test 1: zeros
double[][] array = new double[Rows][];
for (int i = 0; i < array.Length; ++i)
array[i] = new double[Cols];
CompareMethods(array);
// test 2: random values
Random random = new Random();
double[] template = new double[Cols];
for (int i = 0; i < template.Length; ++i)
template[i] = random.NextDouble();
for (int i = 0; i < array.Length; ++i)
array[i] = template;
CompareMethods(array);
}
static void CompareMethods(double[][] array)
{
Stopwatch stopwatch = Stopwatch.StartNew();
for (int i = 0; i < Iterations; ++i)
TestStringJoin(array);
stopwatch.Stop();
Console.WriteLine("TestStringJoin took " + stopwatch.Elapsed);
stopwatch.Reset(); stopwatch.Start();
for (int i = 0; i < Iterations; ++i)
TestStringBuilder(array);
stopwatch.Stop();
Console.WriteLine("TestStringBuilder took " + stopwatch.Elapsed);
}
static void OptimizeForTesting()
{
Thread.CurrentThread.Priority = ThreadPriority.Highest;
Process currentProcess = Process.GetCurrentProcess();
currentProcess.PriorityClass = ProcessPriorityClass.RealTime;
if (Environment.ProcessorCount > 1) {
// use last core only
currentProcess.ProcessorAffinity
= new IntPtr(1 << (Environment.ProcessorCount - 1));
}
}
जब तक कि पूरे कार्यक्रम को चलाने में लगने वाले समय के संदर्भ में 1% अंतर कुछ महत्वपूर्ण हो जाता है, यह सूक्ष्म अनुकूलन जैसा दिखता है। मैं वह कोड लिखूंगा जो सबसे पठनीय / समझने योग्य है और 1% प्रदर्शन अंतर के बारे में चिंता नहीं करता है।
Atwood के बारे में एक महीने पहले इस तरह से संबंधित था:
हाँ। यदि आप कुछ जोड़े से अधिक करते हैं, तो यह बहुत तेज़ होगा।
जब आप एक string.join करते हैं, तो रनटाइम को निम्न करना होता है:
यदि आप दो जोड़ करते हैं, तो इसे दो बार डेटा कॉपी करना होगा, और इसी तरह।
StringBuilder अंतरिक्ष के साथ एक बफर को छोड़ देता है, इसलिए डेटा को मूल स्ट्रिंग की प्रतिलिपि बनाए बिना जोड़ा जा सकता है। जैसा कि बफर में जगह बची है, एपेंडेड स्ट्रिंग को सीधे बफर में लिखा जा सकता है। फिर इसे पूरे स्ट्रिंग को एक बार, अंत में कॉपी करना होगा।