موتر لـ C #. المصفوفات والمتجهات والأنواع المخصصة وسريعة نسبيًا

مرحبا!



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







ماذا نحتاج؟



  1. مصفوفات الأبعاد N (موترات)
  2. تنفيذ مجموعة أساسية من الأساليب للعمل مع موتر كهيكل بيانات
  3. تنفيذ مجموعة أساسية من الوظائف الرياضية (للمصفوفات والمتجهات)
  4. أنواع عامة ، أي أي. والمشغلين المخصصين


وماذا كتب قبلنا؟
, Towel , :



  1. ,
  2. Transpose — «» , O(V), V — «».
  3. , . , , , . , ( , )


System.Numerics.Tensor . , , , . , , .



MathSharp, NumSharp, Torch.Net, TensorFlow, /ML-, .



تخزين العنصر والتبديل والمستشعر الفرعي



سيتم تخزين العناصر في صفيف أحادي البعد. للحصول على عنصر من مجموعة من المؤشرات ، سنضرب المؤشرات في معاملات معينة ونضيفها. أي ، افترض أن لدينا موترًا [3 × 4 × 5]. ثم نحتاج إلى تشكيل مجموعة من ثلاثة عناصر - كتل (هو نفسه جاء بالاسم). ثم العنصر الأخير هو 1 ، والعنصر قبل الأخير هو 5 ، والعنصر الأول 5 * 4 = 20. أي الكتل = [20 ، 5 ، 1]. على سبيل المثال ، عند الوصول إلى الفهرس [1 ، 0 ، 4] ، سيبدو الفهرس في المصفوفة 20 * 1 + 5 * 0 + 4 * 1 = 24. حتى الآن ، كل شيء واضح



تبديل موضع



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



ضع في اعتبارك الوظيفة:



private int GetFlattenedIndexSilent(int[] indices)
{
    var res = 0;
    for (int i = 0; i < indices.Length; i++)
        res += indices[i] * blocks[i];
    return res + LinOffset;
}


كما ترى ، إذا قمت بتبديل الكتل ، فسيتم إنشاء رؤية محاور المبادلة. ولذلك ، فإننا سوف أكتب هذا:



public void Transpose(int axis1, int axis2)
{
    (blocks[axis1], blocks[axis2]) = (blocks[axis2], blocks[axis1]);
    (Shape[axis1], Shape[axis2]) = (Shape[axis2], Shape[axis1]);
}


نحن فقط نغير أعداد المحاور وأطوالها في بعض الأماكن.



فرعي



المستشعر الفرعي للموتر ذو البعد N هو موتر M-dimensional tensor (M <N) ، وهو جزء من الأصل. على سبيل المثال ، العنصر الصفري في موتر الشكل [2 × 3 × 4] هو موتر الشكل [3 × 4]. نحصل عليه بمجرد التحول.



لنتخيل أننا نحصل على مستشعر فرعي في الفهرس n . ثم، العنصر الأول هو ن * كتل [0] + 0 * كتل [1] + 0 * كتل [2] + ... . وهذا يعني أن التحول هو n * كتل [0] . وفقًا لذلك ، بدون نسخ الموتر الأصلي ، نتذكر التحول ، وننشئ موترًا جديدًا مع ارتباط ببياناتنا ، ولكن مع التحول. وستحتاج أيضًا إلى التخلص من العنصر من الكتل ، أي كتل العناصر [0] ، لأن هذا هو المحور الأول ، فلن تكون هناك دعوات إليه.

عمليات التكوين الأخرى



كل الآخرين يتبعون بالفعل من هؤلاء.



  1. سيقوم SetSubtensor بإعادة توجيه العناصر إلى المستشعر الفرعي المطلوب
  2. ينشئ Concat موترًا جديدًا ، وهناك سيقدم عناصر من اثنين (بينما يكون طول المحور الأول هو مجموع أطوال محوري الموترتين)
  3. يجمع المكدس عددًا من الموترات في واحد مع محور إضافي (على سبيل المثال ، المكدس ([3 × 4] ، [3 × 4]) -> [2 × 3 × 4])
  4. إرجاع Slice مكدس من بعض subtenzers


يمكن العثور على جميع عمليات التكوين التي حددتها هنا .



عمليات رياضية



كل شيء بسيط هنا بالفعل.



1) العمليات النقطية (أي بالنسبة لموترين ، يتم تنفيذ العمليات على زوج من العناصر المقابلة (أي ، مع نفس الإحداثيات)). التنفيذ هنا (شرح لسبب وجود هذا الرمز القبيح أدناه).



2) العمليات على المصفوفات. يبدو لي أن المنتج والعكس والعمليات البسيطة الأخرى لا تتطلب أي تفسير. على الرغم من وجود الكثير لتقوله عن المحدد.



حكاية المحدد
, . — , , O(N!). 3x3 ( ).



, . -: float , int .



, , . , .



, . , , , , . . SafeDivisionWrapper .



: . , . SafeDivision ( , ).



3) عمليات قدامى المحاربين (نقطة وعبر المنتج).



الاقوي



القوالب؟


لا توجد قوالب في C # . لذلك ، عليك استخدام العكازات. لقد توصل بعض الأشخاص إلى تجميع ديناميكي في مندوب ، على سبيل المثال ، يقوم بتنفيذ مجموع نوعين.



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



var c = default(TWrapper).Addition(a, b);


وهو مضمن قبل طريقتك. مثال على تنفيذ مثل هذا الهيكل .



الفهرسة


علاوة على ذلك ، على الرغم من أنه يبدو أنه من المنطقي استخدام المعلمات في المفهرس ، أي شيء من هذا القبيل:



public T this[params int[] indices]


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



استثناءات


قمت أيضًا بقيادة جميع الاستثناءات وعمليات التحقق في كتلة #if ALLOW_EXCEPTIONS في حال كنت بحاجة إليها بالتأكيد بسرعة ، وبالتأكيد لا توجد مشاكل مع الفهارس. هناك زيادة طفيفة في الأداء.



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



تعدد


شكرًا بيلي ، اتضح أنه بسيط جدًا وتم تنفيذه عبر Parallel.For.



على الرغم من أن تعدد مؤشرات الترابط ليس حلاً سحريًا ، ويجب تمكينه بعناية. لقد أجريت معيارًا لإضافة الموترات النقطية على i7-7700HQ:







حيث يوضح المحور Y الوقت (بالميكروثانية) لإجراء عملية واحدة باستخدام موترين صحيحين بحجم معين (حجم المحور X).



أي أن هناك حدًا معينًا يكون تعدد مؤشرات الترابط منه منطقيًا. لكي لا تضطر إلى التفكير ، قمت بإنشاء علامة Threading.Auto وقمت بترميز الثوابت بغباء ، بدءًا من وحدة تخزين مساوية يمكنك تمكين تعدد مؤشرات الترابط (هل هناك طريقة تلقائية أكثر ذكاءً؟).



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



مثله



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



قليلا عن الموترات لدينا في AngouriMath
, AM- Entity, . ,



var t = MathS.Matrices.Matrix(3, 3,
              "A", "B", "C",   //      ,     "A * 3",  "sqrt(sin(x) + 5)"
              "D", "E", "F",
              "G", "H", "J");
Console.WriteLine(t.Determinant().Simplify());






A * (E * J - F * H) + C * (D * H - E * G) - B * (D * J - F * G)








All Articles