الحساب السريع للصيغ من Excel في C #

كم مرة تسمع من العملاء أنهم سيرسلون البيانات إلى Excel أو يطلبون منك الاستيراد أو التحميل بتنسيق متوافق مع Excel؟ أنا متأكد من أن Excel في معظم المجالات هو واحد من أكثر الأدوات شعبية وقوية وفي نفس الوقت بسيطة ومريحة. لكن النقطة الأكثر إشكالية دائمًا هي تكامل هذه البيانات مع الأنظمة الآلية المختلفة. طُلب من فريقنا النظر في إمكانية إجراء حسابات البيانات باستخدام الإعدادات من ملف Excel مخصص.

الحساب السريع للصيغ من Excel في C #

إذا كنت بحاجة إلى اختيار مكتبة منتجة للعمل مع ملفات Excel أو إذا كنت تبحث عن حل لحساب البيانات المالية المعقدة (وليس فقط) باستخدام أداة مناسبة لإدارة وتصور الصيغ خارج الصندوق ، مرحبًا بك.

عند دراسة متطلبات مشروع جديد في الشركة ، وجدنا نقطة واحدة مثيرة للاهتمام: "يجب أن يكون الحل المطور قادرًا على حساب تكلفة المنتج (أي الشرفة) عند تغيير التكوين وعرض التكلفة الجديدة على الواجهة على الفور. من الضروري توفير القدرة على تنزيل ملف Excel مع جميع وظائف الحساب وقوائم أسعار المكونات ". يحتاج العميل إلى تطوير بوابة لتصميم تكوين الشرفات اعتمادًا على الأحجام والأشكال وأنواع الزجاج والمواد المستخدمة وأنواع الأربطة ، بالإضافة إلى العديد من المعلمات الأخرى ذات الصلة المستخدمة لحساب التكلفة الدقيقة لمؤشرات الإنتاج والموثوقية.

نقوم بإضفاء الطابع الرسمي على متطلبات الإدخال بحيث يكون من الأسهل فهم أي سياق كان من الضروري حل المشكلة:

  • حساب سريع للأسعار على الواجهة عند تغيير معلمات الشرفة ؛
  • الحساب السريع لبيانات التصميم ، بما في ذلك العديد من التكوينات المختلفة للشرفات والعروض الفردية ، المقدمة من ملف حساب منفصل ؛
  • على المدى الطويل - تطوير الوظائف باستخدام عمليات مختلفة تستهلك الموارد (مهام تحسين المعلمات ، وما إلى ذلك)
  • كل هذا على خادم بعيد مع الإخراج عبر واجهة برمجة التطبيقات ، لأنه جميع الصيغ هي ملكية فكرية للعميل ويجب ألا تكون مرئية لأطراف ثالثة ؛
  • تدفق بيانات الإدخال المحملة بالذروة: يمكن للمستخدم تغيير المعلمات بشكل متكرر وسريع لتحديد تكوين المنتج وفقًا لمتطلباته.

تبدو مغرية للغاية وغير عادية ، دعنا نبدأ!

حلول جاهزة وإعداد البيانات


يبدأ أي بحث لحل المشكلات المعقدة بالتصفح على StackOverflow و GitHub والعديد من المنتديات التي تبحث عن حلول جاهزة.

تم اختيار العديد من الحلول الجاهزة التي تدعم قراءة البيانات من ملفات Excel ، بالإضافة إلى القدرة على إجراء العمليات الحسابية بناءً على الصيغ المحددة داخل المكتبة. من بين هذه المكتبات هناك حلول مجانية تمامًا وتطورات تجارية. 


الخطوة التالية هي كتابة اختبارات التحميل وقياس وقت تشغيل كل مكتبة. للقيام بذلك ، قم بإعداد بيانات الاختبار. نقوم بإنشاء ملف Excel جديد ، ونحدد 10 خلايا لمعلمات الإدخال وصيغة واحدة ( تذكر هذه اللحظة ) للحصول على نتيجة تستخدم جميع معلمات الإدخال. في الصيغة ، نحاول استخدام جميع الوظائف الرياضية الممكنة لمختلف التعقيد الحسابي ودمجها بطريقة صعبة.

لان كما أنها تتعلق بالمال (تكلفة المنتج) ، من المهم أن ننظر إلى دقة النتائج. في هذه الحالة ، سنأخذ القيم الناتجة التي تم الحصول عليها باستخدام Excel Interop كمرجعلان يتم حساب البيانات التي يتم الحصول عليها بهذه الطريقة من خلال Excel الأساسي وهي تساوي القيم التي يراها العملاء عند تطوير الصيغ وحساب التكلفة يدويًا. 

كوقت تشغيل مرجعي ، سنستخدم الكود الأصلي المكتوب يدويًا في C # النقي لعرض نفس الصيغة المكتوبة في Excel.

صيغة الاختبار الأولي المترجمة:

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]);
}

نقوم بتشكيل دفق من بيانات الإدخال العشوائي للتكرارات N (في هذه الحالة ، نستخدم 10000 متجه).

نبدأ الحسابات لكل متجه من معلمات الإدخال في الدفق بأكمله ، والحصول على النتائج وقياس وقت تهيئة المكتبة والحساب العام.

لمقارنة دقة النتائج ، نستخدم مؤشرين - الانحراف المعياري ونسبة القيم المطابقة مع خطوة دقة دقة معينة. إذا قمت بشكل عشوائي بإنشاء قائمة بقيم الإدخال مع الفاصلة العائمة بعد الفاصلة العشرية ، يجب عليك تحديد دقة معلمات الإدخال - وهذا سيسمح لك بالحصول على النتائج الصحيحة. خلاف ذلك ، يمكن أن يكون للأرقام العشوائية اختلاف كبير في حجم الطلبات - يمكن أن يؤثر ذلك بشكل كبير على دقة النتائج ويؤثر على تقدير خطأ النتيجة.

لان في البداية ، نفترض أنه مطلوب للعمل مع قيم ثابتة لتكلفة المواد ، بالإضافة إلى بعض الثوابت من مجالات المعرفة المختلفة ، يمكننا قبول أن جميع معلمات الإدخال سيكون لها قيم دقيقة إلى 3 منازل عشرية. من الناحية المثالية ، تحتاج إلى تحديد نطاق قيم صالح لكل من المعلمات واستخدامها فقط لإنشاء قيم عشوائية ، ولكن منذ تم تجميع صيغة الاختبار بشكل عشوائي دون أي مبرر رياضي ومادي ، ومن ثم لا يمكن حساب مثل هذا النطاق من القيم لجميع المعلمات العشر في فترة زمنية معقولة. لذلك ، في الحسابات قد يكون من الممكن في بعض الأحيان الحصول على خطأ في الحساب. نستثني ناقلات البيانات المدخلة بالفعل في وقت الحساب لمؤشرات الدقة ،لكننا سنحسب مثل هذه الأخطاء كخاصية منفصلة.

اختبار العمارة 


لكل مكتبة منفصلة، تم إنشاء فئتها الخاصة التي تنفذ واجهة ITestExecutor يتضمن 3 طرق - SetUp، Execute و TearDown.

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

طرق SetUpو TearDownتستخدم مرة واحدة فقط في عملية اختبار المكتبة ولا تعتبر عند قياس العمليات الحسابية الوقت على مجموعة كاملة من البيانات المدخلة.

ونتيجة لذلك ، تم اختصار خوارزمية الاختبار إلى ما يلي:

  • إعداد البيانات (نشكل تيارًا من ناقلات الإدخال بدقة معينة) ؛
  • تخصيص الذاكرة للبيانات الناتجة (صفيف من النتائج لكل مكتبة ، صفيف مع عدد الأخطاء) ؛ 
  • تهيئة المكتبات ؛
  • الحصول على نتائج حسابية لكل مكتبة مع حفظ النتيجة في مصفوفة معدة مسبقًا وتسجيل وقت التنفيذ ؛
  • استكمال العمل بقواعد المكتبات ؛
  • تحليل البيانات:

يتم تقديم تمثيل رسومي لهذه الخوارزمية أدناه.

مخطط انسيابي للأداء واختبار الدقة لمكتبات دعم Excel


نتائج التكرار الأول


فهرسمحليEPPlus 4
و EPPlus 5
NPOIسبايراكسل interop
وقت التهيئة (مللي ثانية)02572666321653
الأربعاء وقت التمرير 1 (مللي ثانية)0،00020.40860.68476.978238.8423
المتوسط الانحراف0،0003940،0003950،0002370،000631غير متوفر
صحة99.99٪99.92٪99.97٪99.84٪غير متوفر
أخطاء0،0٪1.94٪1.94٪1.52٪1.94٪

لماذا يتم الجمع بين نتائج EPPlus 5 و EPPlus 4؟
EPPlus . , , . , . EPPlus 5 , . , EPPlus, .

أظهر التكرار الأول للاختبارات نتيجة جيدة ، حيث ظهر القادة بين المكتبات على الفور. كما يتبين من البيانات المذكورة أعلاه ، فإن القائد المطلق في هذه الحالة هو مكتبة EPPlus.

النتائج ليست مثيرة للإعجاب مقارنة بالكود الأصلي ، ولكن يمكنك العيش.

كان يمكن إيقاف هذا ، ولكن بعد التحدث مع العملاء ، تسللت الشكوك الأولى: كانت النتائج جيدة جدًا.


ميزات العمل مع مكتبة Spire
Spire , InvalidCastException. , Excel- , , , . try...catch. , .

أشعل التكرار الأول للاختبارات


أعلاه ، طلبت منك أن ألفت انتباهك إلى نقطة واحدة لعبت دورًا مهمًا في الحصول على النتائج. لقد اشتبهنا في أن الصيغ المستخدمة في ملف Excel الحقيقي للعميل ستكون بعيدة عن البدائية ، مع وجود العديد من التبعيات في الداخل ، لكننا لم نشك في أن هذا قد يؤثر بشكل كبير على المؤشرات. ومع ذلك ، في البداية ، عند تجميع بيانات الاختبار ، لم أكن أتوقع ذلك ، وألمحت البيانات من العميل (على الأقل المعلومات التي تستخدم 120 معلمة إدخال على الأقل في الملف النهائي) إلى أننا بحاجة إلى التفكير وإضافة صيغ ذات تبعيات بين الخلايا .

نبدأ في إعداد بيانات جديدة للتكرار التالي للاختبار. دعنا نتناول 10 معلمات إدخال وإضافة 4 صيغ جديدة إضافية مع الاعتماد فقط على المعلمات وصيغة تجميع واحدة ، والتي تعتمد على هذه الخلايا الأربع مع الصيغ وستعتمد أيضًا على قيم بيانات الإدخال.

الصيغة الجديدة التي سيتم استخدامها للاختبارات اللاحقة:

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));
}

كما ترون ، اتضح أن الصيغة الناتجة أكثر تعقيدًا بكثير ، مما أثر بشكل طبيعي على النتائج - ليس للأفضل. يتم عرض جدول بالنتائج أدناه:

فهرسمحليEPPlus 4 EPPlus 5NPOIسبايراكسل interop
وقت التهيئة (مللي ثانية)02413687221640
الأربعاء وقت التمرير 1 (مللي ثانية)0،00040.9174 (+ 124٪)1.8996 (+ 177٪)7.7647 (+ 11٪)50.7194 (+ 30٪)
المتوسط الانحراف0،0358840.0000000.0000000.000000غير متوفر
صحة98.79٪100.00٪100.00٪100.00٪غير متوفر
أخطاء0،0٪0.3٪0.3٪0.28٪0.3٪


ملاحظة: لأن Excel Interop كبير جدًا ، ويجب استبعاده من المخطط.

كما يتضح من النتائج ، فقد أصبح الوضع غير مناسب تمامًا للاستخدام في الإنتاج. حزين قليلاً ، قم بتخزين القهوة وغوص رأسك في عمق الدراسة - مباشرة في جيل التعليمات البرمجية. 


رمز الجيل


إذا لم تواجه مهمة مماثلة فجأة ، فسنقوم برحلة قصيرة. يعد إنشاء

الشفرة طريقة لحل مشكلة من خلال إنشاء شفرة مصدر ديناميكيًا استنادًا إلى بيانات الإدخال مع التجميع والتنفيذ اللاحقين. يوجد كل من إنشاء التعليمات البرمجية الثابتة أثناء إنشاء المشروع (يمكنني الاستشهاد بـ T4MVC كمثال ، والذي ينشئ رمزًا جديدًا يعتمد على القوالب والبيانات الوصفية التي يمكن استخدامها عند كتابة رمز التطبيق الرئيسي) ، والرمز الديناميكي الذي يتم تشغيله أثناء وقت التشغيل. 

مهمتنا هي تشكيل وظيفة جديدة تستند إلى بيانات المصدر (الصيغ من Excel) ، والتي تتلقى النتيجة بناءً على متجه قيم الإدخال.

للقيام بذلك ، يجب عليك:

  • اقرأ الصيغة من الملف ؛
  • جمع كل التبعيات ؛
  • C#;
  • ;
  • ;
  • .

جميع المكتبات المقدمة مناسبة لقراءة الصيغ ، ومع ذلك ، تبين أن مكتبة EPPlus هي الواجهة الأكثر ملاءمة لمثل هذه الوظائف . بعد أن بحثت قليلاً في شفرة المصدر لهذه المكتبة ، اكتشفت فئات عامة لتشكيل قائمة من الرموز المميزة وتحويلها الإضافي إلى شجرة تعبير. بنغو ، اعتقدت! تعد شجرة التعبير الجاهزة من الصندوق مثالية ، فقط قم بالاطلاع عليها وقم بتكوين رمز C # الخاص بنا. 

لكن صيدًا كبيرًا كان ينتظرني عندما بدأت في دراسة عقد شجرة التعبير الناتجة. بعض العقد ، ولا سيما استدعاء وظائف Excel ، مغلفة معلومات عن الوظيفة المستخدمة ومعلمات الإدخال الخاصة بها ولم توفر أي وصول مفتوح لهذه البيانات. لذلك ، كان لا بد من تأجيل العمل مع شجرة التعبير النهائي.

ننخفض بمستوى واحد ونحاول العمل مع قائمة الرموز. كل شيء بسيط للغاية هنا: لدينا رموز لها نوع وقيمة. لان يتم إعطاؤنا دالة ونحتاج إلى تكوين دالة ، ثم يمكننا بدلاً من ذلك تحويل رموز الشجرة إلى ما يعادلها في C #. الشيء الرئيسي في هذا النهج هو تنظيم وظائف متوافقة. كانت معظم الدالات الرياضية متوافقة بالفعل - مثل حساب جيب التمام ، الجيب ، الحصول على الجذر ، والرفع إلى السلطة. لكن وظائف التجميع - مثل الحد الأقصى للقيمة ، والحد الأدنى ، والمبلغ - يلزم الانتهاء منها. والفرق الرئيسي هو أنه في Excel ، تعمل هذه الدالات مع نطاق من القيم. من أجل بساطة النموذج الأولي ، سنقوم بعمل دوال تأخذ قائمة من المعلمات كمدخلات ، وسعت سابقًا نطاق القيم إلى قائمة خطية.بهذه الطريقة نحصل على التحويل الصحيح والمتوافق من بناء Excel إلى بناء C #. 

فيما يلي الكود الرئيسي لتحويل قائمة الرموز المميزة من صيغة Excel إلى رمز C # صالح.

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();
}

كانت الفروق الدقيقة التالية في التحويل هي استخدام ثوابت Excel - فهي دالات ، لذلك في C # يجب أيضًا أن يتم لفها في دالة. 

يبقى حل سؤال واحد فقط: تحويل مراجع الخلايا إلى معلمة. في الحالة التي يحتوي فيها الرمز المميز على معلومات حول الخلية ، نحدد أولاً ما يتم تخزينه في هذه الخلية. إذا كانت هذه صيغة ، قم بتوسيعها بشكل متكرر. إذا تم استبدال الثابت برابط C # من النموذج p[row, column]، حيث pيمكن أن يكون إما صفيفًا ثنائي الأبعاد أو فئة وصول مفهرسة لتعيين البيانات الصحيحة. مع مجموعة من الخلايا ، نفعل نفس الشيء ، فقط قم بتوسيع النطاق مسبقًا إلى خلايا فردية ومعالجتها بشكل منفصل. وبالتالي ، فإننا نغطي الوظيفة الرئيسية عند ترجمة وظيفة Excel. 


يوجد أدناه رمز لتحويل ارتباط إلى خلية جدول بيانات Excel إلى رمز C # صالح:

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;
}

يبقى فقط لف الكود المحول الناتج في وظيفة ثابتة ، وربط التجميع بوظائف التوافق ، وتجميع التجميع الديناميكي. قم بتحميله في الذاكرة ، واحصل على رابط لوظيفتنا - ويمكنك استخدامه. 

نكتب فئة المجمع لاختبار وتشغيل الاختبارات بقياس الوقت. 

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))
    );
}

نتيجة لذلك ، لدينا نموذج أولي لهذا الحل ونضع عليه علامة EPPlusCompiled ، Mark-I . بعد إجراء الاختبارات ، نحصل على النتيجة التي طال انتظارها. التسارع ما يقرب من 300 مرة. بالفعل ليس سيئًا ، ولكن الكود الناتج لا يزال أبطأ 16 مرة من الكود الأصلي. هل يمكن أن يكون أفضل؟

نعم يمكنك ذلك! دعنا نحاول تحسين النتيجة نظرًا لحقيقة أننا سنستبدل جميع الروابط إلى خلايا إضافية ذات صيغ بمتغيرات. يستخدم اختبارنا استخدامًا متعددًا للخلايا التابعة في الصيغة ، لذلك في الإصدار الأول من المترجم تلقينا حسابات متعددة لنفس البيانات. لذلك ، تقرر استخدام المتغيرات الوسيطة في الحسابات. بعد توسيع الكود باستخدام توليد المتغيرات التابعة ، حصلنا على زيادة في الأداء مرتين أخريين. يسمى هذا التحسنتجميع ، مارك الثاني . يتم عرض جدول المقارنة أدناه:

مكتبةالأربعاء الوقت (مللي ثانية)كويف. التباطؤ
محلي0.000041
تجميع ، مارك الثاني0.0038
تجميع ، مارك الأول0.0061السادس عشر
EPPlus120893023

في ظل هذه الظروف والحدود الزمنية التي تم تخصيصها للمهمة ، حصلنا على نتيجة تقربنا من أداء الشفرة الأصلية بفارق طفيف - 8 مرات ، مقارنة بالإصدار الأصلي - تأخرًا لعدة أوامر من حيث الحجم ، 3028 مرة. ولكن هل من الممكن تحسين النتيجة والاقتراب من الشفرة الأصلية قدر الإمكان ، إذا قمت بإزالة الحدود الزمنية وكم ستكون مناسبة؟

جوابي نعم ، ولكن للأسف ، لم يعد لدي الوقت لتطبيق هذه التقنيات. ربما سأخصص مقالة منفصلة لهذا الموضوع. في الوقت الحالي ، يمكنني فقط التحدث عن الأفكار والخيارات الرئيسية ، وكتابتها في شكل ملخصات قصيرة تم التحقق منها عن طريق التحول العكسي. من خلال التحويل العكسي ، أعني تدهور الكود الأصلي المكتوب يدويًا في اتجاه الكود الذي تم إنشاؤه. يسمح لك هذا النهج بالتحقق من بعض الأطروحات بسرعة كافية ولا يتطلب تغييرات كبيرة في التعليمات البرمجية. كما يسمح لك بالإجابة على سؤال حول كيفية تدهور أداء الكود الأصلي في ظل ظروف معينة ، مما يعني أنه إذا تم تحسين الكود الذي تم إنشاؤه تلقائيًا في الاتجاه المعاكس ، فيمكننا الحصول على تحسين الأداء بمعامل مماثل.

الملخصات


  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،7647507194
المتوسط الانحراف0،0358840،00،00،00،0غير متوفر
صحة98.79٪100.0٪100.0٪100.0٪100.0٪غير متوفر
أخطاء0،0٪0،0٪0.3٪0.3٪0.28٪0.3٪

الحساب السريع للصيغ من Excel في C #

تلخيصًا للخطوات ، لدينا آلية لتحويل الصيغ مباشرةً من مستند Excel مخصص إلى رمز عمل على الخادم. يسمح لك هذا باستخدام المرونة المذهلة لدمج Excel مع أي حل للأعمال دون فقدان واجهة المستخدم القوية التي اعتاد عدد كبير من المستخدمين على التعامل معها. هل كان من الممكن تطوير مثل هذه الواجهة الملائمة مع نفس مجموعة الأدوات لتحليل وتصور البيانات كما في Excel لفترة تطوير قصيرة؟

وما هي أكثر عمليات التكامل غير العادية والمثيرة للاهتمام مع مستندات Excel التي كان عليك تنفيذها؟

All Articles