Я ищу самый быстрый (общий подход) к преобразованию строк в различные типы данных на ходу.
Я разбираю файлы больших текстовых данных, сгенерированные чем-то (файлы размером несколько мегабайт). Эта особенная функция считывает строки в текстовом файле, анализирует каждую строку в столбцах на основе разделителей и помещает проанализированные значения в .NET DataTable. Это позже вставляется в базу данных. Мое узкое место по FAR - это преобразования строк (Convert и TypeConverter).
Мне нужно идти динамически (т.е. избегать формы "Convert.ToInt32" и т.д.), потому что я никогда не знаю, какие типы будут в файлах. Тип определяется более ранней конфигурацией во время выполнения.
До сих пор я пробовал следующее и оба брали несколько минут для разбора файла. Обратите внимание, что если я прокомментирую эту строку, она пробежит всего несколько сотен миллисекунд.
row[i] = Convert.ChangeType(columnString, dataType);
И
TypeConverter typeConverter = TypeDescriptor.GetConverter(type);
row[i] = typeConverter.ConvertFromString(null, cultureInfo, columnString);
Если кто-нибудь знает более быстрый способ, подобный этому, я хотел бы узнать об этом. Или, если по какой-то причине мой подход просто отстой, я открыт для предложений. Но, пожалуйста, не указывайте мне на не общие подходы с использованием жестко закодированных типов; это просто не вариант.
UPDATE - многопоточность для повышения производительности
Чтобы повысить производительность, я рассмотрел разделение задач синтаксического анализа на несколько потоков. Я обнаружил, что скорость несколько увеличилась, но все же не так сильно, как я надеялся. Тем не менее, вот мои результаты для тех, кто заинтересован.
Система:
Intel Xenon 3.3GHz Quad Core E3-1245
Память: 12,0 ГБ
Windows 7 Enterprise x64
Тест:
Эта тестовая функция:
(1) Получить массив строк. (2) Разделите строку разделителями. (3) Разбирайте строки в типы данных и храните их в строке. (4) Добавить строку в таблицу данных. (5) Повторите (2) - (4) до завершения.
Тест включал 1000 строк, каждая строка анализировалась на 16 столбцов, так что общее число переходов строк составляет 16000 строк. Я тестировал один поток, 4 потока (из-за четырехъядерного ядра) и 8 потоков (из-за гиперпоточности). Поскольку я только хруст данных здесь, я сомневаюсь, добавив больше потоков, чем это принесет пользу. Таким образом, для одного потока он анализирует 1000 строк, 4 потока обрабатывают 250 строк каждый, а 8 потоков обрабатывают 125 строк каждый. Также я проверил несколько различных способов использования потоков: создание потоков, пул потоков, задачи и объекты функций.
Результаты: Время выполнения - миллисекунды.
Одиночная тема:
- Вызов метода: 17720
4 Темы
- Параметрированный запуск темы: 13836
- ThreadPool.QueueUserWorkItem: 14075
- Task.Factory.StartNew: 16798
- Func BeginInvoke EndInvoke: 16733
8 Темы
- Параметрированный старт: 12591
- ThreadPool.QueueUserWorkItem: 13832
- Task.Factory.StartNew: 15877
- Func BeginInvoke EndInvoke: 16395
Как вы видите, самый быстрый из них - это параметр Parameterized Thread Start с 8 потоками (число моих логических ядер). Однако он не сильно использует 4 потока, и только на 29% быстрее, чем использование одного ядра. Конечно, результаты будут зависеть от машины. Также я застрял с
Dictionary<Type, TypeConverter>
кэш для синтаксического анализа строк, поскольку использование массивов преобразователей типов не обеспечивало заметного увеличения производительности, и наличие одного общего кэшированного конвертера типов более удобно, а не создавать массивы повсюду, когда они мне нужны.
ДРУГОЕ ОБНОВЛЕНИЕ:
Итак, я провел еще несколько тестов, чтобы увидеть, могу ли я выжать еще больше производительности, и я нашел несколько интересных вещей. Я решил придерживаться 8 потоков, все началось с метода параметризованного потока старта (который был самым быстрым из моих предыдущих тестов). Тот же тест, что и выше, был запущен, только с различными алгоритмами синтаксического анализа. Я заметил, что
Convert.ChangeType and TypeConverter
взять примерно такое же количество времени. Тип конкретных преобразователей типа
int.TryParse
немного быстрее, но не вариант для меня, так как мои типы являются динамическими. У ricovox были хорошие советы по обработке исключений. У моих данных действительно есть недопустимые данные, некоторые целые столбцы помещают тире '-' для пустых чисел, поэтому преобразователи типов взорвутся на это: значение каждой строки, которую я анализирую, имеет как минимум одно исключение, это 1000 исключений! Очень много времени.
Кстати, вот как я делаю свои преобразования с TypeConverter. Расширения - это просто статический класс, и GetTypeConverter просто возвращает cahced TypeConverter. Если во время преобразования выбраны исключения, используется значение по умолчанию.
public static Object ConvertTo(this String arg, CultureInfo cultureInfo, Type type, Object defaultValue)
{
Object value;
TypeConverter typeConverter = Extensions.GetTypeConverter(type);
try
{
// Try converting the string.
value = typeConverter.ConvertFromString(null, cultureInfo, arg);
}
catch
{
// If the conversion fails then use the default value.
value = defaultValue;
}
return value;
}
Результаты:
Те же тесты на 8 потоках - разобрать 1000 строк, по 16 столбцов, по 250 строк на поток.
Итак, я сделал 3 новые вещи.
1 - Запустите тест: проверьте известные недопустимые типы перед разбором, чтобы минимизировать исключения. т.е. если (! Char.IsDigit(c)) value = 0; OR columnString.Contains('-') и т.д.
Время выполнения: 29 мс
2 - Запустите тест: используйте собственные алгоритмы синтаксического анализа, которые имеют блоки try catch.
Время выполнения: 12424мс
3 - Запустите тест: используйте алгоритмы синтаксического анализа для проверки недопустимых типов перед синтаксическим разбором, чтобы минимизировать исключения.
Время выполнения 15мс
Ничего себе! Как вы видите, устранение исключений привело к разнице в мире. Я никогда не понимал, насколько дорогими исключениями были! Таким образом, если я минимизирую свои исключения из TRULY неизвестных случаев, тогда алгоритм синтаксического анализа будет выполняться на три порядка быстрее. Я рассматриваю это абсолютно решительно. Я считаю, что я буду поддерживать динамическое преобразование типов с помощью TypeConverter, это всего на несколько миллисекунд медленнее. Проверка известных недопустимых типов перед конвертированием исключает исключения и ускоряет работу! Благодаря ricovox за то, что я указал, что это заставило меня проверить это дальше.