Код VB.NET/COM Server намного медленнее, чем код Excel VBA - PullRequest
1 голос
/ 01 сентября 2009

Фон

У меня есть клиент, которому нужен код Excel VBA, который создает значения формул, перемещенные в VB.NET. Он занимается предоставлением финансовой аналитики, в данном случае - надстройкой для Excel. Я перевел VBA в код VB.NET, который работает в отдельной DLL. DLL скомпилирована как COM-сервер, потому что, ну, в общем, должны быть вызваны Excel .NET UDF. Пока все хорошо: ячейки Excel имеют «= foo (Range1, Range2, ...)», вызывается UDF VB.NET Com Server, и ячейка получает значение, соответствующее значению кода VBA.

Проблема

Код VB.NET намного медленнее. Я могу растянуть ряд формул на основе VBA и получить мгновенный расчет. Я могу растянуть сопоставимый диапазон формул на основе VB.NET, и вычисление занимает 5-10 секунд. Это заметно медленнее и неприемлемо для клиента.

Есть несколько возможностей, которые мне приходят в голову:

  1. Собственная компиляция VBA быстрее из-за отсутствия переключателя
  2. DLL может быть загружена и выгружена для каждого вызова UDF
  3. DLL вызывает методы Excel WorksheetFunction и требует объект Application, а создание объекта Application стоит дорого
  4. вызов метода Excel WorksheetFunction из DLL стоит дорого

Я не думаю, что (2) верно, потому что я помещаю вызовы для добавления к файлу в функции Shared New, Public New и Finalize, и все, что я получаю, это:

Shared Sub New
Public Sub New
Finalize

когда я открываю электронную таблицу, несколько раз растягиваю диапазон формул и закрываю электронную таблицу.

Я не думаю, что (3) верно, поскольку запись в файл показывает, что объект Application создается только один раз.

Вопрос

Как мне выяснить, что занимает время? Как профилировать в этой среде? Есть ли очевидные улучшения?

В последней категории я попытался уменьшить количество созданий объекта Application (используемого для вызовов WorkSheetFunction), сделав его Shared:

<Guid("1ECB17BB-444F-4a26-BC3B-B1D6F07D670E")> _
<ClassInterface(ClassInterfaceType.AutoDual)> _
<ComVisible(True)> _
<ProgId("Library.Class")> _
Public Class MyClass
    Private Shared Appp As Application ' Very annoying

Подходы приняты

Я пытался уменьшить зависимость от математических функций Excel, переписав свою собственную. Я заменил Min, Max, Average, Stdev, Small, Percentile, Skew, Kurtosis и еще несколько. Мой код UDF вызывает в Excel гораздо меньше. Кажется, что неизбежный вызов принимает Range в качестве аргумента и преобразует его в массив .NET для внутреннего использования.

Ответы [ 8 ]

3 голосов
/ 01 сентября 2009

DLL скомпилирована как COM-сервер потому что, хорошо, Excel-вызываемый .NET UDF должны быть

Немного шоу-стопора, если это правда, я согласен. Но, конечно, это совсем не так, иначе я бы так начал ...

Вы можете написать свои пользовательские функции в C ++ для Excel SDK и доставить их как XLL, с одной стороны. Это обычная практика для количественных аналитиков в банках; на самом деле им это нравится, что говорит о них как о группе.

Другой, менее болезненный вариант, с которым я только недавно сталкивался, это ExcelDNA , который, AFAICT, обеспечивает неприятный бит SDK / XLL способом подключения ваших .NET DLL. Достаточно круто, что он даже позволяет загружать исходный код, а не создавать отдельную DLL, которая отлично подходит для создания прототипов (он использует тот факт, что CLR на самом деле содержит компилятор). Я не знаю о производительности: я не пытался ее тестировать, но, похоже, она обошла проблему COM Interop, которая, как известно, ужасна.

Кроме того, я могу только поддержать другие рекомендации: как можно меньше ссылаться на свою книгу, ее содержание и приложение Excel. Каждый звонок стоит.

2 голосов
/ 12 октября 2009

В CodeDlex есть еще кое-что для ExcelDna: http://exceldna.codeplex.com/Wiki/View.aspx?title=ExcelDna%20Performance.

Для действительно простых функций издержки на вызов управляемой функции через ExcelDna очень малы, что позволяет выполнять несколько сотен тысяч вызовов UDF в секунду.

2 голосов
/ 10 сентября 2009

Я недавно провел сравнительный анализ перемещения данных из Excel в .NET с использованием различных продуктов / методов. Все методы .NET, которые я пробовал, были медленнее, чем VBA и VB6, но лучшие из них смогли использовать интерфейс XLL, который дал лучшие результаты, чем интерфейс автоматизации. эталонный тест был достаточно оптимизирован (перенос диапазонов в массивы и т. д.) результаты были (миллисекунды для моего теста)

  • VB6 COM addin 63

    C XLL 37

    Addin Express Automation VB.net 170

    Addin Express XLL VB.net 100

    ExcelDNA XLL CVB.Net 81

Управляемый XLL дал сравнимое время, но также позволяет обычным маршалерам быть быстрым.

2 голосов
/ 02 сентября 2009

Я серьезно полагаю, что взаимодействие с VB.NET на COM-сервер осуществляется через маршаллинг.В VBA методы вызывались напрямую - управление передавалось в них за пару инструкций процессора, и это выглядело очень быстро.Теперь с помощью маршаллинга выполняется целый набор дополнительной работы, и каждый вызов сталкивается с серьезными накладными расходами.Вам нужно либо серьезно сократить количество вызовов (сделать каждый вызов более эффективным), либо отключить сортировку и работать так, как если бы это было с VBA.См. этот вопрос для получения подробной информации о том, как возможно выполнить последнее.

1 голос
/ 01 сентября 2009

У меня такой же опыт, как у Джо. Это в основном медленное взаимодействие.

В большинстве случаев это может быть решено путем работы с целыми диапазонами вместо отдельных ячеек. Это можно сделать с помощью массивов .Net и передать их в / из Excel за один вызов.

например.

Dim values(10,10) As object

Dim r As Excel.Range = Me.Range("A1")
r = r.Resize(UBound(values, 1), UBound(values,2))
values = r.Value

For ii = 0 To UBound(values,1)
    For jj = 0 To UBound(values,2)
        values(ii,jj) = CType(values(ii,jj), Double)*2
    Next
Next

r.Value = values

Это решило все проблемы с производительностью, которые я видел

1 голос
/ 01 сентября 2009

Мое предположение, основанное на большом опыте использования Excel через COM Interop, заключается в том, что это переключение контекста и / или отображение данных из внутренних структур данных Excel в объекты .NET.

SpreadsheetGear for .NET может быть вариантом для вас. Это намного быстрее, чем Excel через COM Interop (посмотрите, что говорят некоторые клиенты здесь ), и он поддерживает совместимые с Excel вычисления и пользовательские функции (см. Образец пользовательских функций на на этой странице ). .

Вы можете скачать бесплатную пробную версию здесь , если хотите попробовать.

Отказ от ответственности: я владею SpreadsheetGear LLC

0 голосов
/ 26 июля 2016

Действительно опоздал на этот вопрос (7 лет), но что бы это ни стоило, я работал над 5/6 отдельными системами Excel в инвестиционных банках и видел похожий шаблон проектирования во всех их системах Excel, который я опишу.

Да, у них есть блоки ячеек, которые содержат связанные данные, такие как список цен государственных облигаций, но они не всегда пропускают этот блок ячеек. Вместо этого они создадут объект, который находится в памяти, который доступен глобально и помечен дескриптором. Объект содержит копию содержимого ячейки, и поэтому к нему проще обращаться в аналитическом коде.

Таким образом, дескриптор примера будет

'USTreasuries(103450|2016-07-25T15:33)' 

, где видно, что «103450» - это номер объекта, достаточно уникальный для того, чтобы получить объект из словаря с глобальной областью действия (скажем), отметка времени представляет собой момент создания объекта, а USTreasuries представляет собой удобное описание. Можно создать такой объект с формулой как-то так:

=CreateHandledObject("USTreasuries",A1:D30)

Тот написал бы аналитику, которая принимает этот дескриптор и получает данные внутри. Для этого необходимо, чтобы CreateHandledObject () был помечен как изменчивый, и вам необходимо переключить расчет на ручной и выполнить пересчет по коду или по пользователю.

Ваши проблемы возникают из-за бесконечного сбора данных с листа. Я думаю, что этот подход поможет вам свести этот громоздкий элемент к минимуму.

0 голосов
/ 10 февраля 2010

Одна мысль. Вместо передачи объекта Range (может случиться так, что каждый вызов объекта Ranbe можно маршалировать из .Net в Excel), сопоставьте все ваши параметры в базовые типы, значения типа double, строки, типизированные массивы и при необходимости нетипизированные массивы вариантов, и передать их в .Net DLL. Таким образом, вам нужно только составить вариант.

- DM

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...