Я «унаследовал» некоторое устаревшее программное обеспечение для работы с большими текстовыми файлами, которое в настоящее время написано в Visual Foxpro (в частности, версии 7).
Я изучаю варианты замены этих программ чем-то новым, которое будет легче изменять и поддерживать. Однако я не могу приблизиться к производительности Foxpro при импорте больших текстовых файлов.
Например, у меня есть текстовый файл размером ~ 1 ГБ с ~ 110 000 записей, каждая из которых имеет 213 полей (каждая строка имеет длину 9247 символов), с которой я тестирую.
Используя Foxpro:
? SECONDS()
USE new
APPEND FROM test.dat TYPE sdf
USE
? SECONDS()
Этот импорт в базу данных на моем компьютере выполняется менее чем за 10 секунд.
Используя C # / SQLite (с Filehelpers и System.Data.SQLite), я смог выполнить импорт быстрее всего за минуту. Я попытался оптимизировать это как можно лучше, используя предложения из этого вопроса (например, транзакции и т. д.). На самом деле минута для импорта файла размером 1 ГБ не кажется плохой, если я не сравнивал ее с 10 секундами для Foxpro.
Примерная разбивка времени, потраченного на ~ 1 минуту, составляет:
Filehelpers reading file and parsing: 12 seconds.
Building SQL commands from Filehelpers object: 15 seconds.
Running individual ExecuteNonQuery()'s: 24 seconds.
Committing transactions (every 5000 records): 12 seconds.
По сравнению со связанным потоком мои вставки в секунду намного медленнее, но в моих записях 213 полей против 7, так что это ожидаемо. Если я разбиваю его по полям в секунду, я получаю примерно 360 000 против 630 000 в потоке. Скорость вставки по мегабайтам составляет ~ 2,24 мегабайт / с для другого плаката и 15,4 мегатбайт / с для меня. Поэтому я думаю, что моя производительность сравнима с показателями другого постера, и, вероятно, я не смогу сделать еще тонну оптимизации.
Почему это намного медленнее, чем импорт Foxpro (в 5-6 раз медленнее)? Разве это просто яблоки перед апельсинами, и я должен просто смириться с меньшей скоростью в обмен на другие преимущества, которые я получаю от использования новых технологий?
РЕДАКТИРОВАТЬ:
Вот тестовый код, который я выполнил со случайными данными, показывающими схожую скорость .... Правильно ли я реализую транзакции? ExecuteNonQuery () занимает значительное время. Я реализовал все команды SQL с помощью простых, не параметризованных, скорее всего, неэффективных манипуляций со строками, но на данный момент меня это не слишком беспокоит (я буду читать из файлов, и этот метод был намного быстрее, чем использование структуры сущностей ).
public class Program
{
public static void Main(string[] args)
{
TestSqlite(100, 20000);
}
public static void TestSqlite(int numColumns, int numRows)
{
Console.WriteLine("Starting Import.... columns: " + numColumns.ToString() + " rows: " + numRows.ToString());
var conn = new SQLiteConnection(@"Data Source=C:\vsdev\SqliteTest\src\SqliteTest\ddic.db;Version=3");
conn.Open();
var cmd = new SQLiteCommand(conn);
cmd.CommandText = "DROP TABLE IF EXISTS test";
cmd.ExecuteNonQuery();
string createCmd = "CREATE TABLE 'test'('testId' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ";
for (var i = 0; i < numColumns; i++)
{
createCmd += "'test" + i.ToString() + "' TEXT, ";
}
createCmd = createCmd.Substring(0, createCmd.Length - 2);
createCmd += ")";
cmd.CommandText = createCmd;
cmd.ExecuteNonQuery();
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
var transaction = conn.BeginTransaction();
int lineCount = 0;
long startTime;
long endTime;
long totalStringTime = 0;
long totalAddTime = 0;
long totalSaveTime = 0;
string command;
for (var l = 0; l < numRows; l++)
{
startTime = stopWatch.ElapsedMilliseconds;
command = CreateRandomInsert("test", numColumns);
endTime = stopWatch.ElapsedMilliseconds;
totalStringTime += endTime - startTime;
///Execute Query
startTime = stopWatch.ElapsedMilliseconds;
cmd.CommandText = command;
cmd.ExecuteNonQuery();
endTime = stopWatch.ElapsedMilliseconds;
totalAddTime += endTime - startTime;
if (lineCount > 5000)
{
lineCount = 0;
startTime = stopWatch.ElapsedMilliseconds;
transaction.Commit();
transaction.Dispose();
transaction = conn.BeginTransaction();
cmd = new SQLiteCommand(conn);
endTime = stopWatch.ElapsedMilliseconds;
totalSaveTime += endTime - startTime;
Console.Write('.');
}
lineCount += 1;
}
startTime = stopWatch.ElapsedMilliseconds;
transaction.Commit();
transaction.Dispose();
endTime = stopWatch.ElapsedMilliseconds;
totalSaveTime += endTime - startTime;
Console.WriteLine('.');
Console.WriteLine("String time: " + totalStringTime.ToString());
Console.WriteLine("ExecuteNonQuery time: " + totalAddTime.ToString() + ", per 1000 records: " + (totalAddTime / (numRows / 1000)).ToString());
Console.WriteLine("Commit time: " + totalSaveTime.ToString() + ", per 1000 records: " + (totalSaveTime / (numRows / 1000)).ToString());
stopWatch.Stop();
TimeSpan ts = stopWatch.Elapsed;
string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}",
ts.Hours, ts.Minutes, ts.Seconds,
ts.Milliseconds / 10);
Console.WriteLine(" in " + elapsedTime);
conn.Close();
}
public static string CreateRandomInsert(string TableName, int numColumns)
{
List<string> nameList = new List<string>();
List<string> valueList = new List<string>();
for (var i = 0; i < numColumns; i++)
{
nameList.Add("test" + i.ToString());
valueList.Add(Guid.NewGuid().ToString());
}
return CreateSql(TableName, nameList, valueList);
}
public static string CreateSql(string TableName, List<string> names, List<string> values)
{
string textCommand = "";
textCommand += "INSERT INTO " + TableName + " (";
foreach (var nameVal in names)
{
textCommand += nameVal + ", ";
}
textCommand = textCommand.Substring(0, textCommand.Length - 2);
textCommand += ") VALUES (";
foreach (var val in values)
{
textCommand += "'" + val + "', ";
}
textCommand = textCommand.Substring(0, textCommand.Length - 2);
textCommand += ");";
return textCommand;
}
}