أنا لا أحترم التغليف ، أو استخدم نوعًا مختلفًا من جدول الطريقة للاتصال بالطرق الخاصة بسرعة

مرحبا بالجميع. أود أن أشارك مثالاً على استخدام StructLayout لشيء أكثر إثارة للاهتمام من الأمثلة مع وحدات البايت والعلامات الداخلية والأرقام الأخرى ، حيث يحدث كل شيء أكثر قليلاً من المتوقع.

قبل الشروع في انتهاك التغليف بسرعة البرق ، باختصار ، تجدر الإشارة إلى ما هو StructLayout. بالمعنى الدقيق للكلمة ، هذه حتى StructLayoutAttribute ، وهي سمة تسمح لك بإنشاء هياكل وفئات مماثلة للتوحيد في C ++. بمزيد من التفصيل ، تسمح لك هذه السمة بالتحكم في وضع أعضاء الفصل في الذاكرة. وفقا لذلك ، يتم وضعه فوق الفصل. عادة ، إذا كان للفصل حقلين ، نتوقع أن يتم ترتيبهما بالتسلسل ، أي أنهم سيكونون مستقلين عن بعضهم البعض (لا تتداخل). ومع ذلك ، يتيح برنامج StructLayout تحديد أن موقع الحقول لن يتم تحديده بواسطة البيئة ، ولكن بواسطة المستخدم. للإشارة صراحة إلى إزاحة الحقول ، استخدم المعلمة LayoutKind.Explicit. للإشارة إلى الإزاحة المتعلقة ببداية الفئة / البنية (المشار إليها فيما يلي باسم الفئة) ، نرغب في وضع الحقل ، يجب أن نضع سمة FieldOffset فوقه ، والتي تأخذ كمعلمة عدد وحدات البايت - المسافة البادئة من بداية الفصل. لا يمكن تمرير قيمة سالبة ، لذلك لا تفكر حتى في إفساد المؤشرات إلى جدول الأسلوب أو فهرس كتلة المزامنة ، فستكون أكثر تعقيدًا قليلاً.

لنبدأ في كتابة الرمز. في البداية ، أقترح البدء بمثال بسيط. قم بإنشاء فئة من النموذج التالي:
public class CustomClass { public override string ToString() { return "CUSTOM"; } public virtual object Field { get; } = new object(); } 

بعد ذلك ، نستخدم الآلية المذكورة أعلاه لضبط الإزاحات الميدانية بشكل صريح.
 [StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClass SomeInstance; } 

في الوقت الحالي ، سأؤجل التفسيرات واستخدم الصف المكتوب على النحو التالي:
  class Program { static void Main(string[] args) { CustomStructWithLayout instance = new CustomStructWithLayout(); instance.SomeInstance = new CustomClass(); instance.Str = "4564"; Console.WriteLine(instance.SomeInstance.GetType()); //System.String Console.WriteLine(instance.SomeInstance.ToString()); //4564 Console.Read(); } } 

المجموع إرجاع استدعاء الأسلوب GetType () سلسلة ، أسلوب ToString () غير مطيع ويعطينا السلسلة "4564".
إفرازات الدماغ: ما الذي سيتم عرضه عندما يتم استدعاء الخاصية الافتراضية CustomClass؟

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

هنا يمكنك أن ترى بعض الأشياء غير العادية. الأول هو أنه في الكائن ، تحتوي العناوين في جداول الطريقة على حقول مختلفة لحقول الفئة ، كما هو متوقع ، ولكن عنوان قيمة الحقل واحد. الثاني - يمكنك أن ترى أن كلا الحقلين يقعان في الإزاحة 4. أعتقد أن معظمهم سيفهمون ، ولكن فقط في حالة ، سأوضح ، مباشرة على عنوان الكائن هناك ارتباط إلى جدول الأساليب. تبدأ الحقول بإزاحة 4 بايت (لـ z 32 بت) ، ويقع فهرس كتلة المزامنة مع إزاحة -4.

الآن بعد أن اكتشفت ما يحدث ، يمكنك محاولة استخدام الإزاحات لاستدعاء شيء لا ينبغي استدعاؤه.
للقيام بذلك ، كررت بنية فئة الأوتار في إحدى فصولي. صحيح ، لقد كررت البداية فقط ، لأن فئة الأوتار ضخمة جدًا.
  public class CustomClassLikeString { public const int FakeAlignConst = 3; public const int FakeCharPtrAlignConst = 3; public static readonly object FakeStringEmpty; public char FakeFirstChar; public int FakeLength = 3; public const int FakeTrimBoth = 3; public const int FakeTrimHead = 3; public const int FakeTrimTail = 3; public CustomClassLikeString(){} public CustomClassLikeString(int a){} public CustomClassLikeString(byte a){} public CustomClassLikeString(short a){} public CustomClassLikeString(string a){} public CustomClassLikeString(uint a){} public CustomClassLikeString(ushort a){} public CustomClassLikeString(long a){ } public void Stub1(){} public virtual int CompareTo(object value) { return 800; } public virtual int CompareTo(string value) { return 801; } } 

حسنًا ، تتغير البنية مع التنسيق قليلاً
  [StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public string Str; [FieldOffset(0)] public CustomClassLikeString SomeInstance; } 

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

لذا ، الأداء. والواقع أن المنافس المباشر في تحدي ما هو غير ضروري للتسبب وفي انتهاك التغليف هو التفكير. أعتقد أنه من الواضح بالفعل أننا أسرع من هذا الشيء ، جميعنا لا نحلل البيانات الوصفية. القيم الدقيقة:
الطريقةالوظيفةيعنيخطأستديفمتوسط
StructLayoutFieldClr0.0597 نانوثانية0.0344 نانوثانية0.0396 نانوثانية0.0498 نانوثانية
حقل التأملClr197.1257 نانوثانية1.9148 نانوثانية1.7911 نانوثانية197.4787 نانوثانية
طريقة التركيبClr3.5195 نانوثانية0.0382 نانوثانية0.0319 نانوثانية3.5285 نانوثانية
طريقة التفكيرClr743.9793 نانوثانية13.7378 نانوثانية12.8504 نانوثانية743.8471 نانوثانية
في ما يلي جزء طويل من الكود مع كيفية قياس الأداء (إذا احتاجه شخص ما):
كود
  [ClrJob] [RPlotExporter, RankColumn] [InProcessAttribute] public class Benchmarking { private CustomStructWithLayout instance; private string str; [GlobalSetup] public void Setup() { instance = new CustomStructWithLayout(); instance.SomeInstance = new CustomClassLikeString(); instance.Str = "4564"; str = "4564"; } [Benchmark] public int StructLayoutField() { return instance.SomeInstance.FakeLength; } [Benchmark] public int ReflectionField() { return (int)typeof(string).GetField("m_stringLength", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(str); } [Benchmark] public int StructLayoutMethod() { return instance.SomeInstance.CompareTo("4564"); } [Benchmark] public int ReflectionMethod() { return (int)typeof(string).GetMethod("CompareTo", new[] { typeof(string) }).Invoke(str, new[] { "4564" }); } } class Program { static void Main(string[] args) { var summary = BenchmarkRunner.Run<Benchmarking>(); } } 

Source: https://habr.com/ru/post/ar423657/


All Articles