Производительность C # / SQLite по сравнению с FoxPro при импорте файлов

Я «унаследовал» некоторое устаревшее программное обеспечение для работы с большими текстовыми файлами, которое в настоящее время написано в 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;
    }
}

person playsted    schedule 07.01.2016    source источник
comment
Хотите попробовать это?   -  person randrade86    schedule 08.01.2016
comment
К сожалению, мой файл имеет фиксированную ширину поля, возможно, я конвертирую в CSV и попробую прямой импорт для проверки производительности.   -  person playsted    schedule 08.01.2016


Ответы (1)


Когда это данные фиксированной ширины, VFP имеет преимущество перед другими базами данных. Низкий уровень, это почти тот же формат, в котором VFP хранит свои данные. Например, если у вас нет полей, несовместимых с Foxpro 2.x, то импорт этих данных будет просто означать добавление байта 0x20 перед каждой строкой (+ запись заголовка файла, если он еще не был записан) - и обновление индексов, если таковые имеются, это отнимет много времени. Если вам нужно было разобрать строки, тогда VFP довольно медленно выполняет строковые операции по сравнению с C #.

(Я не знал FileHelpers, спасибо, что указали на него)

Интересны 9247 байт фиксированных данных и 213 полей. Я хотел бы протестировать вставку в SQLite, postgreSQL, SQL server и некоторые базы данных NoSQL. Не могли бы вы поделиться пустым dbf (или просто структурой в виде кода или xml - возможно, с 1-2 строками образцов данных из файла)? Я не пробовал раньше с 200+ полями, иначе 1 минута для 110K строк звучит медленно.

Таблица VFP сможет добавлять такие данные только 2 раза, и вам нужно будет создать другую таблицу. Вы говорите «манипулирование» текстовыми файлами, но не комментируете, что это за манипуляция. Может быть, прямое «манипулирование» без добавления в таблицу более эффективно и быстрее, а весь процесс завершается быстрее? Просто мысль.

person Cetin Basoz    schedule 09.01.2016
comment
Отличная информация Цетин. Я подумал, что должно быть что-то принципиально более простое в том, что делает Foxpro. Я постараюсь собрать для вас образец файла. Если вы думаете, что скорость вставки низкая, я продолжу экспериментировать с настройками, чтобы также ее оптимизировать. - person playsted; 10.01.2016
comment
К вашему сведению, я добавил код со случайными данными, показывающий аналогичные скорости вставки. - person playsted; 11.01.2016