Schnelle Berechnung von Formeln aus Excel in C #

Wie oft hören Sie von Kunden, dass sie Daten an Excel senden oder Sie auffordern, diese in einem Excel-kompatiblen Format zu importieren oder hochzuladen? Ich bin sicher, dass Excel in den meisten Bereichen eines der beliebtesten, leistungsfähigsten und gleichzeitig einfachsten und bequemsten Tools ist. Der problematischste Punkt ist jedoch immer die Integration solcher Daten in verschiedene automatisierte Systeme. Unser Team wurde gebeten, die Möglichkeit zu prüfen, Datenberechnungen mithilfe der Einstellungen aus einer benutzerdefinierten Excel-Datei durchzuführen.

Schnelle Berechnung von Formeln aus Excel in C #

Wenn Sie eine produktive Bibliothek für die Arbeit mit Excel-Dateien auswählen müssen oder nach einer Lösung für die Berechnung komplexer Finanzdaten (und nicht nur) mit einem praktischen Tool zum sofortigen Verwalten und Visualisieren von Formeln suchen, sind Sie bei cat willkommen.

Bei der Untersuchung der Anforderungen eines neuen Projekts im Unternehmen fanden wir einen sehr interessanten Punkt: „Die entwickelte Lösung sollte in der Lage sein, die Kosten des Produkts (nämlich des Balkons) beim Ändern der Konfiguration zu berechnen und die neuen Kosten sofort auf der Benutzeroberfläche anzuzeigen. Es ist erforderlich, die Möglichkeit zum Herunterladen einer Excel-Datei mit allen Berechnungsfunktionen und Komponentenpreislisten bereitzustellen. “ Der Kunde musste ein Portal für die Gestaltung der Konfiguration von Balkonen entwickeln, das von den Größen, Formen, Arten der Verglasung und verwendeten Materialien, den Arten der Befestigungen sowie vielen anderen verwandten Parametern abhängt, die zur Berechnung der genauen Produktionskosten und der Zuverlässigkeitsindikatoren verwendet werden.

Wir formalisieren die Eingabeanforderungen, damit leichter zu verstehen ist, in welchem ​​Kontext das Problem gelöst werden musste:

  • Schnelle Berechnung der Preise auf der Schnittstelle beim Ändern der Parameter des Balkons;
  • Schnelle Berechnung von Konstruktionsdaten, einschließlich vieler verschiedener Konfigurationen von Balkonen und individuellen Angeboten, bereitgestellt durch eine separate Berechnungsdatei;
  • Langfristig - die Entwicklung von Funktionen mithilfe verschiedener ressourcenintensiver Vorgänge (Aufgaben zur Optimierung von Parametern usw.)
  • All dies ist auf einem Remote-Server mit Ausgabe über API, weil Alle Formeln sind geistiges Eigentum des Kunden und sollten für Dritte nicht sichtbar sein;
  • Spitzenlast-Eingabedatenstrom: Der Benutzer kann häufig und schnell Parameter ändern, um die Produktkonfiguration entsprechend seinen Anforderungen auszuwählen.

Es klingt sehr ungewöhnlich und sehr verlockend, fangen wir an!

Schlüsselfertige Lösungen und Datenaufbereitung


Jede Recherche zur Lösung komplexer Probleme beginnt mit dem Surfen in StackOverflow, GitHub und vielen Foren, um nach vorgefertigten Lösungen zu suchen.

Es wurden mehrere vorgefertigte Lösungen ausgewählt, die das Lesen von Daten aus Excel-Dateien sowie das Ausführen von Berechnungen basierend auf in der Bibliothek angegebenen Formeln unterstützen. Unter diesen Bibliotheken gibt es sowohl völlig kostenlose Lösungen als auch kommerzielle Entwicklungen. 


Der nächste Schritt besteht darin, Auslastungstests zu schreiben und die Laufzeit jeder Bibliothek zu messen. Bereiten Sie dazu Testdaten vor. Wir erstellen eine neue Excel-Datei, definieren 10 Zellen für die Eingabeparameter und eine ( diesen Moment merken ) Formel, um ein Ergebnis zu erhalten, das alle Eingabeparameter verwendet. In der Formel versuchen wir, alle möglichen mathematischen Funktionen unterschiedlicher Rechenkomplexität zu nutzen und auf knifflige Weise zu kombinieren.

weil Es geht auch um Geld (die Kosten des Produkts), es ist wichtig, die Genauigkeit der Ergebnisse zu überprüfen. In diesem Fall nehmen wir die resultierenden Werte, die mit Excel Interop erhalten wurden, als Referenzda Die auf diese Weise erhaltenen Daten werden über den Excel-Kern berechnet und entsprechen den Werten, die Kunden bei der Entwicklung von Formeln und der manuellen Berechnung der Kosten sehen. 

Als Referenzlaufzeit verwenden wir nativen Code, der manuell in reinem C # geschrieben wurde, um dieselbe in Excel geschriebene Formel anzuzeigen.

Übersetzte erste Testformel:

public double Execute(double[] p)
{
    return Math.Pow(p[0] * p[8] / p[4] * Math.Sin(p[5]) * Math.Cos(p[2]) +
                          Math.Abs(p[1] - (p[2] + p[3] + p[4] + p[5] + p[6] + p[7] + p[8]))
                          * Math.Sqrt(p[0] * p[0] + p[1] * p[1]) / 2.0 * Math.PI, p[9]);
}

Wir bilden einen Strom von zufälligen Eingabedaten für N Iterationen (in diesem Fall verwenden wir 10.000 Vektoren).

Wir starten die Berechnungen für jeden Vektor von Eingabeparametern im gesamten Stream, erhalten die Ergebnisse und messen den Zeitpunkt der Initialisierung der Bibliothek und die allgemeine Berechnung.

Um die Genauigkeit der Ergebnisse zu vergleichen, verwenden wir zwei Indikatoren - die Standardabweichung und den Prozentsatz der übereinstimmenden Werte mit einem bestimmten Genauigkeitsschritt epsilon. Wenn Sie zufällig eine Liste von Eingabewerten mit einem Gleitkomma nach dem Dezimalpunkt erstellen, müssen Sie die Genauigkeit der Eingabeparameter bestimmen. Auf diese Weise erhalten Sie die richtigen Ergebnisse. Andernfalls können Zufallszahlen einen großen Unterschied in Größenordnungen aufweisen - dies kann die Genauigkeit der Ergebnisse stark beeinflussen und die Schätzung des Fehlers des Ergebnisses beeinflussen.

weil Zunächst gehen wir davon aus, dass es erforderlich ist, mit konstanten Werten der Materialkosten sowie einigen Konstanten aus verschiedenen Wissensgebieten zu arbeiten. Wir können akzeptieren, dass alle Eingabeparameter Werte haben, die auf 3 Dezimalstellen genau sind. Im Idealfall müssen Sie für jeden Parameter einen gültigen Wertebereich angeben und nur diese verwenden, um zufällige Werte zu generieren Die Testformel wurde ohne mathematische und physikalische Begründung zufällig zusammengestellt. Dann ist es nicht möglich, einen solchen Wertebereich für alle 10 Parameter in einem angemessenen Zeitraum zu berechnen. Daher ist es bei Berechnungen manchmal möglich, einen Berechnungsfehler zu erhalten. Wir schließen solche Eingabedatenvektoren bereits zum Zeitpunkt der Berechnung für Genauigkeitsindikatoren aus.aber wir werden solche Fehler als separates Merkmal zählen.

Testarchitektur 


Für jede separate Bibliothek wurde eine eigene Klasse geschaffen, die eine Schnittstelle implementiert , ITestExecutor die drei Methoden enthalten - SetUp, Execute und TearDown.

public interface ITestExecutor
{
    //      
    void SetUp();
    //   ,           
    double Execute(double[] p);
    //    ,      
    void TearDown();
}

Methoden SetUpund TearDownwerden nur einmal beim Testen der Bibliothek verwendet und werden bei der Messung von Zeitberechnungen für den gesamten Satz von Eingabedaten nicht berücksichtigt.

Infolgedessen beschränkte sich der Testalgorithmus auf Folgendes:

  • Datenaufbereitung (wir bilden einen Strom von Eingabevektoren mit einer bestimmten Genauigkeit);
  • Zuweisung von Speicher für die resultierenden Daten (ein Array von Ergebnissen für jede Bibliothek, ein Array mit der Anzahl von Fehlern); 
  • Initialisierung von Bibliotheken;
  • Abrufen von Berechnungsergebnissen für jede der Bibliotheken durch Speichern des Ergebnisses in einem vorbereiteten Array und Aufzeichnen der Ausführungszeit;
  • Abschluss der Arbeit mit dem Code der Bibliotheken;
  • Analyse der Daten:

Eine grafische Darstellung dieses Algorithmus ist unten dargestellt.

Flussdiagramm der Leistungs- und Genauigkeitstests von Excel-Unterstützungsbibliotheken


Ergebnisse der ersten Iteration


IndexEinheimischEPPlus 4
& EPPlus 5
NPOITurmExcel Interop
Initialisierungszeit (ms)02572666321653
Heiraten Zeit für 1 Durchgang (ms)0,00020,40860,68476.978238,8423
Durchschn Abweichung0,0003940,0003950,0002370,000631n / a
Richtigkeit99,99%99,92%99,97%99,84%n / a
Fehler0,0%1,94%1,94%1,52%1,94%

Warum werden die Ergebnisse von EPPlus 5 und EPPlus 4 kombiniert?
EPPlus . , , . , . EPPlus 5 , . , EPPlus, .

Die erste Iteration der Tests zeigte ein gutes Ergebnis, bei dem die Leiter der Bibliotheken sofort sichtbar wurden. Wie aus den obigen Daten ersichtlich ist, ist die EPPlus-Bibliothek in dieser Situation der absolute Marktführer.

Die Ergebnisse sind im Vergleich zum nativen Code nicht beeindruckend, aber Sie können leben.

Dies hätte gestoppt werden können, aber nach einem Gespräch mit Kunden schlichen sich die ersten Zweifel ein: Die Ergebnisse waren zu gut.


Funktionen für die Arbeit mit der Spire-Bibliothek
Spire , InvalidCastException. , Excel- , , , . try...catch. , .

Rechen der ersten Iteration von Tests


Oben habe ich Sie gebeten, Ihre Aufmerksamkeit auf einen Punkt zu lenken, der eine wichtige Rolle bei der Erzielung von Ergebnissen spielte. Wir hatten den Verdacht, dass die in der realen Excel-Datei des Kunden verwendeten Formeln mit vielen Abhängigkeiten alles andere als primitiv sind, aber wir hatten nicht den Verdacht, dass dies die Indikatoren stark beeinflussen könnte. Bei der Zusammenstellung der Testdaten habe ich dies jedoch zunächst nicht vorausgesehen, und die Daten des Kunden (zumindest die Information, dass mindestens 120 Eingabeparameter in der endgültigen Datei verwendet werden) deuteten darauf hin, dass wir Formeln mit Abhängigkeiten zwischen Zellen denken und hinzufügen müssen .

Wir bereiten neue Daten für die nächste Testiteration vor. Lassen Sie uns auf 10 Eingabeparameter eingehen und weitere 4 neue Formeln hinzufügen, die nur von Parametern und 1 Aggregationsformel abhängen. Diese basieren auf diesen vier Zellen mit Formeln und basieren auch auf den Werten der Eingabedaten.

Die neue Formel, die für nachfolgende Tests verwendet wird:

public double Execute(double[] p)
{
    var price1 = Math.Pow(p[0] * p[8] / p[4] * Math.Sin(p[5]) * Math.Cos(p[2]) +
                          Math.Abs(p[1] - (p[2] + p[3] + p[4] + p[5] + p[6] + p[7] + p[8]))
                          * Math.Sqrt(p[0] * p[0] + p[1] * p[1]) / 2.0 * Math.PI, p[9]);

    var price2 = p[4] * p[5] * p[2] / Math.Max(1, Math.Abs(p[7]));

    var price3 = Math.Abs(p[7] - p[3]) * p[2];

    var price4 = Math.Sqrt(Math.Abs(p[1] * p[2] + p[3] * p[4] + p[5] * p[6]) + 1.0);

    var price5 = p[0] * Math.Cos(p[1]) + p[2] * Math.Sin(p[1]);

    var sum = p[0] + p[1] + p[2] + p[3] + p[4] + p[5] + p[6] + p[7] + p[8] + p[9];

    var price6 = sum / Math.Max(1, Math.Abs((p[0] + p[1] + p[2] + p[3]) / 4.0))
                 + sum / Math.Max(1, Math.Abs((p[4] + p[5] + p[6] + p[7] + p[8] + p[9]) / 6.0));

    var pricingAverage = (price1 + price2 + price3 + price4 + price5 + price6) / 6.0;

    return pricingAverage / Math.Max(1, Math.Abs(price1 + price2 + price3 + price4 + price5 + price6));
}

Wie Sie sehen können, stellte sich heraus, dass die resultierende Formel viel komplizierter war, was sich natürlich auf die Ergebnisse auswirkte - nicht zum Besseren. Eine Tabelle mit den Ergebnissen ist unten dargestellt:

IndexEinheimischEPPlus 4 EPPlus 5NPOITurmExcel Interop
Initialisierungszeit (ms)02413687221640
Heiraten Zeit für 1 Durchgang (ms)0,00040,9174 (+ 124%)1,8996 (+ 177%)7,7647 (+ 11%)50,7194 (+ 30%)
Durchschn Abweichung0,0358840,0000000,0000000,000000n / a
Richtigkeit98,79%100,00%100,00%100,00%n / a
Fehler0,0%0,3%0,3%0,28%0,3%


Hinweis: Weil Excel Interop ist zu groß, musste aus dem Diagramm ausgeschlossen werden.

Wie aus den Ergebnissen hervorgeht, ist die Situation für den Einsatz in der Produktion völlig ungeeignet geworden. Ein bisschen traurig, Kaffee auffüllen und tiefer in die Studie eintauchen - direkt in die Codegenerierung. 


Codegenerierung


Wenn Sie plötzlich nie mehr vor einer ähnlichen Aufgabe standen, werden wir einen kurzen Ausflug durchführen. 

Die Codegenerierung ist eine Möglichkeit, ein Problem zu lösen, indem Quellcode basierend auf Eingabedaten dynamisch generiert und anschließend kompiliert und ausgeführt wird. Es gibt sowohl statische Codegenerierung, die während der Erstellung des Projekts auftritt (als Beispiel kann ich T4MVC anführen, das neuen Code basierend auf Vorlagen und Metadaten erstellt, die beim Schreiben des Hauptanwendungscodes verwendet werden können), als auch dynamischen Code, der zur Laufzeit ausgeführt wird. 

Unsere Aufgabe ist es, eine neue Funktion basierend auf den Quelldaten (Formeln aus Excel) zu bilden, die das Ergebnis basierend auf dem Vektor der Eingabewerte empfängt.

Dazu müssen Sie:

  • Lesen Sie die Formel aus der Datei.
  • Sammle alle Abhängigkeiten;
  • C#;
  • ;
  • ;
  • .

Alle vorgestellten Bibliotheken eignen sich zum Lesen von Formeln. Die EPPlus- Bibliothek erwies sich jedoch als die bequemste Schnittstelle für solche Funktionen . Nachdem ich ein bisschen im Quellcode dieser Bibliothek gestöbert hatte, entdeckte ich öffentliche Klassen, um eine Liste von Token zu erstellen und diese weiter in einen Ausdrucksbaum umzuwandeln. Bingo, dachte ich! Ein vorgefertigter Ausdrucksbaum aus der Box ist ideal. Gehen Sie ihn einfach durch und bilden Sie unseren C # -Code. 

Aber ein großer Haken wartete auf mich, als ich anfing, die Knoten des resultierenden Ausdrucksbaums zu untersuchen. Einige Knoten, insbesondere der Aufruf von Excel-Funktionen, kapselten Informationen über die verwendete Funktion und ihre Eingabeparameter und boten keinen offenen Zugriff auf diese Daten. Daher musste die Arbeit mit dem fertigen Ausdrucksbaum verschoben werden.

Wir gehen eine Ebene tiefer und versuchen, mit der Liste der Token zu arbeiten. Hier ist alles ganz einfach: Wir haben Token, die Typ und Wert haben. weil Wir erhalten eine Funktion und müssen eine Funktion bilden. Dann können wir stattdessen die Baum-Token in das Äquivalent in C # konvertieren. Die Hauptsache bei diesem Ansatz ist die Organisation kompatibler Funktionen. Die meisten mathematischen Funktionen waren bereits kompatibel - wie das Berechnen des Cosinus, des Sinus, das Erhalten der Wurzel und das Erhöhen auf eine Potenz. Die Aggregationsfunktionen wie Maximalwert, Minimalwert und Betrag mussten jedoch finalisiert werden. Der Hauptunterschied besteht darin, dass diese Funktionen in Excel mit einem Wertebereich arbeiten. Zur Vereinfachung des Prototyps werden wir Funktionen erstellen, die eine Liste von Parametern als Eingabe verwenden und zuvor den Wertebereich in eine lineare Liste erweitern.Auf diese Weise erhalten wir die korrekte und kompatible Konvertierung von Excel-Syntax in C # -Syntax. 

Unten finden Sie den Hauptcode zum Konvertieren einer Liste von Token aus einer Excel-Formel in einen gültigen C # -Code.

private string TransformToSharpCode(string formula, ParsingContext parsingContext)
{
    // Initialize basic compile components, e.g. lexer
    var lexer = new Lexer(parsingContext.Configuration.FunctionRepository, parsingContext.NameValueProvider);

    // List of dependency variables that can be filled during formula transformation
    var variables = new Dictionary<string, string>();
    using (var scope = parsingContext.Scopes.NewScope(RangeAddress.Empty))
    {
        // Take resulting code line
        var compiledResultCode = TransformToSharpCode(formula, parsingContext, scope, lexer, variables);

        var output = new StringBuilder();

        // Define dependency variables in reverse order.
        foreach (var variableDefinition in variables.Reverse())
        {
            output.AppendLine($"var {variableDefinition.Key} = {variableDefinition.Value};");
        }

        // Take the result
        output.AppendLine($"return {compiledResultCode};");

        return output.ToString();
    }
}

private string TransformToSharpCode(ICollection<Token> tokens, ParsingContext parsingContext, ParsingScope scope, ILexer lexer, Dictionary<string, string> variables)
{
    var output = new StringBuilder();

    foreach (Token token in tokens)
    {
        switch (token.TokenType)
        {
            case TokenType.Function:
                output.Append(BuildFunctionName(token.Value));
                break;
            case TokenType.OpeningParenthesis:
                output.Append("(");
                break;
            case TokenType.ClosingParenthesis:
                output.Append(")");
                break;
            case TokenType.Comma:
                output.Append(", ");
                break;
            case TokenType.ExcelAddress:
                var address = token.Value;
                output.Append(TransformAddressToSharpCode(address, parsingContext, scope, lexer, variables));
                break;
            case TokenType.Decimal:
            case TokenType.Integer:
            case TokenType.Boolean:
                output.Append(token.Value);
                break;
            case TokenType.Operator:
                output.Append(token.Value);
                break;
        }
    }

    return output.ToString();
}

Die nächste Nuance bei der Konvertierung war die Verwendung von Excel-Konstanten - sie sind Funktionen, daher müssen sie in C # auch in eine Funktion eingeschlossen werden. 

Es bleibt nur eine Frage zu lösen: die Konvertierung von Zellreferenzen in einen Parameter. In dem Fall, in dem das Token Informationen über die Zelle enthält, bestimmen wir zuerst, was in dieser Zelle gespeichert ist. Wenn dies eine Formel ist, erweitern Sie sie rekursiv. Wenn die Konstante durch einen C # -Analog-Link der Form ersetzt wird p[row, column], pkann es sich entweder um ein zweidimensionales Array oder eine indizierte Zugriffsklasse für eine korrekte Datenzuordnung handeln. Bei einer Reihe von Zellen machen wir dasselbe, erweitern den Bereich einfach auf einzelne Zellen und verarbeiten sie separat. Daher behandeln wir die Hauptfunktionalität bei der Übersetzung einer Excel-Funktion. 


Unten finden Sie den Code zum Konvertieren eines Links zu einer Excel-Tabellenzelle in einen gültigen C # -Code:

private string TransformAddressToSharpCode(string excelAddress, ParsingContext parsingContext, ParsingScope scope, ILexer lexer, Dictionary<string, string> variables)
{
    // Try to parse excel range of addresses
    // Currently, reference to another worksheet in address string is not supported

    var rangeParts = excelAddress.Split(':');
    if (rangeParts.Length == 1)
    {
        // Unpack single excel address
        return UnpackExcelAddress(excelAddress, parsingContext, scope, lexer, variables);
    }

    // Parse excel range address
    ParseAddressToIndexes(rangeParts[0], out int startRowIndex, out int startColumnIndex);
    ParseAddressToIndexes(rangeParts[1], out int endRowIndex, out int endColumnIndex);

    var rowDelta = endRowIndex - startRowIndex;
    var columnDelta = endColumnIndex - startColumnIndex;

    var allAccessors = new List<string>(Math.Abs(rowDelta * columnDelta));

    // TODO This part of code doesn't support reverse-ordered range address
    for (var rowIndex = startRowIndex; rowIndex <= endRowIndex; rowIndex++)
    {
        for (var columnIndex = startColumnIndex; columnIndex <= endColumnIndex; columnIndex++)
        {
            // Unpack single excel address
            allAccessors.Add(UnpackExcelAddress(rowIndex, columnIndex, parsingContext, scope, lexer, variables));
        }
    }

    return string.Join(", ", allAccessors);
}

private string UnpackExcelAddress(string excelAddress, ParsingContext parsingContext, ParsingScope scope, ILexer lexer, Dictionary<string, string> variables)
{
    ParseAddressToIndexes(excelAddress, out int rowIndex, out int columnIndex);
    return UnpackExcelAddress(rowIndex, columnIndex, parsingContext, scope, lexer, variables);
}

private string UnpackExcelAddress(int rowIndex, int columnIndex, ParsingContext parsingContext, ParsingScope scope, ILexer lexer, Dictionary<string, string> variables)
{
    var formula = parsingContext.ExcelDataProvider.GetRangeFormula(_worksheet.Name, rowIndex, columnIndex);
    if (string.IsNullOrWhiteSpace(formula))
    {
        // When excel address doesn't contain information about any excel formula, we should just use external input data parameter provider.
        return $"p[{rowIndex},{columnIndex}]";
    }

    // When formula is provided, try to identify that variable is not defined yet
    // TODO Worksheet name is not included in variable name, potentially that can cause conflicts
    // Extracting and reusing calculations via local variables improves performance for 0.0045ms
    var cellVariableId = $"C{rowIndex}R{columnIndex}";
    if (variables.ContainsKey(cellVariableId))
    {
        return cellVariableId;
    }

    // When variable does not exist, transform new formula and register that to variable scope
    variables.Add(cellVariableId, TransformToSharpCode(formula, parsingContext, scope, lexer, variables));

    return cellVariableId;
}

Es bleibt nur, den resultierenden konvertierten Code in eine statische Funktion zu verpacken, die Assembly mit Kompatibilitätsfunktionen zu verknüpfen und die dynamische Assembly zu kompilieren. Laden Sie es in den Speicher, erhalten Sie einen Link zu unserer Funktion - und Sie können es verwenden. 

Wir schreiben eine Wrapper-Klasse zum Testen und führen Tests mit Zeitmessung durch. 

public void SetUp()
{
    // Initialize excel package by EPPlus library
    _package = new ExcelPackage(new FileInfo(_fileName));
    _workbook = _package.Workbook;
    _worksheet = _workbook.Worksheets[1];

    _inputRange = new ExcelRange[10];
    for (int rowIndex = 0; rowIndex < 10; rowIndex++)
    {
        _inputRange[rowIndex] = _worksheet.Cells[rowIndex + 1, 2];
    }

    // Access to result cell and extract formula string
    _resultRange = _worksheet.Cells[11, 2];

    var formula = _resultRange.Formula;

    // Initialize parsing context and setup data provider
    var parsingContext = ParsingContext.Create();
    parsingContext.ExcelDataProvider = new EpplusExcelDataProvider(_package);

    // Transform Excel formula to CSharp code
    var sourceCode = TransformToSharpCode(formula, parsingContext);

    // Compile CSharp code to IL dynamic assembly via helper wrappers
    _code = CodeGenerator.CreateCode<double>(
        sourceCode,
        new string[]
        {
            // List of used namespaces
            "System", // Required for Math functions
            "ExcelCalculations.PerformanceTests" // Required for Excel function wrappers stored at ExcelCompiledFunctions static class
        },
        new string[]
        {
            // Add reference to current compiled assembly, that is required to use Excel function wrappers located at ExcelCompiledFunctions static class
            "....\\bin\\Debug\\ExcelCalculations.PerformanceTests.exe"
        },
        // Notify that this source code should use parameter;
        // Use abstract p parameter - interface for values accessing.
        new CodeParameter("p", typeof(IExcelValueProvider))
    );
}

Als Ergebnis haben wir einen Prototyp dieser Lösung und markieren ihn als EPPlusCompiled, Mark-I . Nach dem Ausführen der Tests erhalten wir das lang erwartete Ergebnis. Die Beschleunigung beträgt fast das 300-fache. Schon nicht schlecht, aber der resultierende Code ist immer noch 16-mal langsamer als der native. Könnte es besser sein?

Ja, du kannst! Versuchen wir, das Ergebnis zu verbessern, da wir alle Links zu zusätzlichen Zellen durch Formeln mit Variablen ersetzen. Unser Test verwendet die mehrfache Verwendung abhängiger Zellen in der Formel, sodass wir in der ersten Version des Übersetzers mehrere Berechnungen derselben Daten erhalten haben. Daher wurde beschlossen, bei den Berechnungen Zwischenvariablen zu verwenden. Nachdem wir den Code mithilfe generierter Variablen erweitert haben, konnten wir die Leistung noch zweimal steigern. Diese Verbesserung heißtEPPlusCompiled, Mark-II . Die Vergleichstabelle ist unten dargestellt:

BibliothekHeiraten Zeit (ms)Coef. Verlangsamungen
Einheimisch0,000041
EPPlusCompiled, Mark-II0,0038
EPPlusCompiled, Mark-I0,0061Sechszehn
EPPlus1,20893023

Unter diesen Bedingungen und den für die Aufgabe zugewiesenen Fristen haben wir ein Ergebnis erhalten, das uns der Leistung des nativen Codes mit einer leichten Verzögerung näher bringt - 8-mal im Vergleich zur Originalversion - einer Verzögerung von mehreren Größenordnungen, 3028-mal. Aber ist es möglich, das Ergebnis zu verbessern und dem nativen Code so nahe wie möglich zu kommen, wenn Sie die Fristen entfernen und wie viel wird angemessen sein?

Meine Antwort lautet ja, aber leider hatte ich keine Zeit mehr, diese Techniken zu implementieren. Vielleicht widme ich diesem Thema einen eigenen Artikel. Im Moment kann ich nur über die wichtigsten Ideen und Optionen sprechen und sie in Form von kurzen Abstracts schreiben, die durch umgekehrte Transformation verifiziert wurden. Mit umgekehrter Konvertierung meine ich die Verschlechterung des von Hand geschriebenen nativen Codes in Richtung des generierten Codes. Dieser Ansatz ermöglicht es Ihnen, einige Thesen schnell genug zu überprüfen und erfordert keine wesentlichen Änderungen im Code. Außerdem können Sie die Frage beantworten, wie sich die Leistung des nativen Codes unter bestimmten Bedingungen verschlechtert. Wenn der generierte Code automatisch in die entgegengesetzte Richtung verbessert wird, können Sie eine Leistungsverbesserung mit einem ähnlichen Koeffizienten erzielen.

Abstracts


  1. , ;
  2. inline ;
  3. - Sum, Max, Min ;
  4. Sum inline ;
  5. ( ) .


NativeEPPlus Compiled, Mark-IIEPPlus 4EPPlus 5NPOISpireExcel Interop
()02392413687221640
. 1 ()0,00040,0030,91741,89967,764750,7194
Durchschn Abweichung0,0358840,00,00,00,0n / a
Richtigkeit98,79%100,0%100,0%100,0%100,0%n / a
Fehler0,0%0,0%0,3%0,3%0,28%0,3%

Schnelle Berechnung von Formeln aus Excel in C #

Zusammenfassend haben wir einen Mechanismus zum Konvertieren von Formeln direkt aus einem benutzerdefinierten Excel-Dokument in Arbeitscode auf dem Server. Auf diese Weise können Sie die unglaubliche Flexibilität der Integration von Excel in jede Geschäftslösung nutzen, ohne die leistungsstarke Benutzeroberfläche zu verlieren, mit der eine große Anzahl von Benutzern vertraut ist. War es möglich, für einen so kurzen Entwicklungszeitraum eine so bequeme Benutzeroberfläche mit denselben Tools zur Analyse und Visualisierung von Daten wie in Excel zu entwickeln?

Und was waren die ungewöhnlichsten und interessantesten Integrationen in Excel-Dokumente, die Sie implementieren mussten?

All Articles