Показать сообщение отдельно
Старый 13.06.2006, 19:19   #29  
Gustav is offline
Gustav
Moderator
Аватар для Gustav
SAP
Лучший по профессии 2009
 
1,858 / 1152 (42) ++++++++
Регистрация: 24.01.2006
Адрес: Санкт-Петербург
Записей в блоге: 19
Ну, я сам немножко поковырялся... не с целью бития рекордов, а как раз наоборот – с целью небольшой разборки, направленной на выяснения некоторого предела низкой скорости - типа "медленнее которой остается только способ вписывания значений в ячейки Excel шариковой ручкой"

Абстрагируемся на некоторое время от точности получаемых результатов (я имею в виду, в частности, потенциальные проблемы с пресловутыми ведущими нулями в текстовых значениях, состоящих из цифр, а также потерю точности в длинных цифровых текстовых кодах, например, в 20-тизначных банковских счетах, где возможно обнуление последних 5 разрядов) и сосредоточимся исключительно на скорости вывода (пусть пока текст "000333" пишется в ячейку Excel как число 333, а дата "13.06.06" как число 38881 - просто тупо посмотрим на длительность всего процесса в целом).

Способы, представленные AndyD и Recoilme, демонстрируют высокую скорость выгрузки.

Задался вопросом: а как поведет себя на тестовом задании популярный класс ComExcelDocument_RU? Примерно представлял, что "фигово" (о низкой скорости уже "слышал" на Форуме). Но захотелось оценить меру "фиговости", поэтому обо всём по порядку.

Этот класс в своем первозданном виде (слой DIS) предоставляет нам в распоряжение 2 своих метода, которые можно использовать для решения нашей тестовой задачи:

1. метод static str numToNameCell(int _iCol, int _iRow) - например, для получения текстового адреса ячейки "B1" по номеру строки = 1 и по номеру столбца = 2
2. метод public void insertValue(BookMark _bookMark, anyType _anyVal, int _workSheet = 1) - для записи значения в эту ячейку "B1".

Набросал тестовый джоб следующего вида:
X++:
static void SpeedTest_Job1(Args _args)
{
    LedgerTrans             ledgerTrans;
    LedgerTable             ledgerTable;
    ComExcelDocument_RU     excel;
    int row;
    int timeFullStart, timeFullFinish, timeFullTotal;
    ;
 
    timeFullStart = timenow();
 
    excel = new ComExcelDocument_RU();
    excel.newFile("", true, -1);
 
    row = 0;
    while select  ledgerTrans
            join  ledgerTable
            where ledgerTrans.AccountNum == ledgerTable.AccountNum
    {
        row++;
        if (row > 50000) break;
 
        excel.insertValue(ComExcelDocument_RU::numToNameCell( 1, row), ledgerTrans.RecId);
        excel.insertValue(ComExcelDocument_RU::numToNameCell( 2, row), ledgerTrans.AccountNum);
        excel.insertValue(ComExcelDocument_RU::numToNameCell( 3, row), ledgerTable.AccountName);
        excel.insertValue(ComExcelDocument_RU::numToNameCell( 4, row), strfmt('%1', ledgerTable.AccountPlType));
        excel.insertValue(ComExcelDocument_RU::numToNameCell( 5, row), ledgerTrans.BondBatchTrans_RU);
        excel.insertValue(ComExcelDocument_RU::numToNameCell( 6, row), ledgerTrans.BondBatch_RU);
        excel.insertValue(ComExcelDocument_RU::numToNameCell( 7, row), ledgerTrans.TransDate);
        excel.insertValue(ComExcelDocument_RU::numToNameCell( 8, row), ledgerTrans.Txt);
        excel.insertValue(ComExcelDocument_RU::numToNameCell( 9, row), ledgerTrans.AmountMST);
        excel.insertValue(ComExcelDocument_RU::numToNameCell(10, row), strfmt('%1', ledgerTrans.Crediting));
    }
 
    timeFullFinish = timenow();
 
    timeFullTotal = timeFullFinish - timeFullStart;
 
    info('Время выполнения, сек');
    info(int2str(timeFullTotal));
 
}
Джоб был выполнен на достаточно мощном сервере с процессором AMD, 2.5 GHz, 4 Gb. БД - на другой машине, Oracle. Сетка - 100 Mb. Excel - 2003. Axapta - 3.0 CIS SP3, без AOS.

Время выполнения джоба - 3615 сек, т.е. практически ровно 1 час.

Для сравнения: время выполнения на этом же компе джобов AndyD и Recoilme - 29 сек и 12 сек соответственно.

Решил заглянуть внутрь этих двух методов класса ComExcelDocument_RU. Заглянул. Расстроился… Есть ощущение, что адресация к ячейкам воплощалась по принципу "Что вижу в Excel - о том пою. Как вручную работаю - так и запрограммирую. Вижу ячейки в первой строке Excel - A1, B1, C1... но, однако, смекаю, что вывод на лист, скорее всего, будет производиться в двух циклах: внешний - по строкам, тут всё в порядке: 1, 2, 3, 4, 5... и внутренний - по столбцам - здесь неудобство: порядок чисел-номеров колонок 1, 2, 3, 4, 5... нужно превращать в буквы А, B, C, D, E... С одной целью - чтобы потом сцепить букву столбца и цифру строки и выйти на какой-нибудь Range("A1"), и дальше через метод insertValue задать значение свойства Value2".

Возникает вопрос: А ЗА-ЧЕМ так, мягко говоря, сложно?

Со времен версии Excel 5 (1994 год) - первой версии Excel, когда вместо специфического макроязыка версии 4 пришёл Visual Basic - так вот, со времен версии 5 в Excel присутствует способ адресации к ячейкам листа при помощи метода Cells(номер строки, номер столбца), предназначенный как раз для нужд циклического перебора ячеек. Для обращения к прямоугольным диапазонам ячеек используется формат Range(Cells(1-я строка диапазона, 1-й столбец диап-на), Cells(посл. Строка диап-на, посл.столбец диап-на)) - а не только адресация вида Range("A1:H150"), как это пишет макрорекордер на VBA в Excel. Существуют другие полезные методы адресации, например, Offset...

Набросал второй тестовый джоб:

X++:
static void SpeedTest_Job2(Args _args)
{
    LedgerTrans             ledgerTrans;
    LedgerTable             ledgerTable;
    ComExcelDocument_RU     excel;
 
    COM doc;
    COM actSheet;
    COM cells;
 
    int row;
    int timeFullStart, timeFullFinish, timeFullTotal;
    ;
 
    timeFullStart = timenow();
 
    excel = new ComExcelDocument_RU();
    excel.newFile("", true, -1);
 
    doc = excel.getComDocument();
    actSheet = doc.ActiveSheet();
    cells = actSheet.Cells();
 
    row = 0;
    while select  ledgerTrans
            join  ledgerTable
            where ledgerTrans.AccountNum == ledgerTable.AccountNum
    {
        row++;
        if (row > 50000) break;
 
        COM::createFromVariant( cells.Item(row,  1) ).Value2( ledgerTrans.RecId );
        COM::createFromVariant( cells.Item(row,  2) ).Value2( ledgerTrans.AccountNum );
        COM::createFromVariant( cells.Item(row,  3) ).Value2( ledgerTable.AccountName );
        COM::createFromVariant( cells.Item(row,  4) ).Value2( strfmt('%1', ledgerTable.AccountPlType) );
        COM::createFromVariant( cells.Item(row,  5) ).Value2( ledgerTrans.BondBatchTrans_RU );
        COM::createFromVariant( cells.Item(row,  6) ).Value2( ledgerTrans.BondBatch_RU );
        COM::createFromVariant( cells.Item(row,  7) ).Value2( ledgerTrans.TransDate );
        COM::createFromVariant( cells.Item(row,  8) ).Value2( ledgerTrans.Txt );
        COM::createFromVariant( cells.Item(row,  9) ).Value2( ledgerTrans.AmountMST );
        COM::createFromVariant( cells.Item(row, 10) ).Value2( strfmt('%1', ledgerTrans.Crediting) );
 
    }
 
 
    timeFullFinish = timenow();
 
    timeFullTotal = timeFullFinish - timeFullStart;
 
    info('Время выполнения, сек');
    info(int2str(timeFullTotal));
 
}
Время выполнения - 1090 сек. Т.е. 18 минут. Или более, чем в 3 раза быстрее, чем первый джоб с "классической" адресацией ComExcelDocument_RU. Конечно, до "29 сек и 12 сек" - "как до Пекина...", но уже гораздо более оптимистично, чем "1 час". Троекратное увеличение скорости только за счет изменения способа адресации.

Опытные коллеги наверняка знают обо всем об этом. Обидно за новичков, которые приходят в Аксу, сталкиваются с ComExcelDocument_RU как с неким "стандартом де-факто" и первое время вынуждены идти по этому, как мне кажется, не совсем верному пути... Теряя это самое драгоценное время!
За это сообщение автора поблагодарили: mazzy (5), DreamCreator (3), JeS (1), Silphidae (1).