معرفی
پروژه منبع باز CodexMicroORM در GitHub دارای چندین ویژگی است که به شما کمک می کند تا سریع و مختصر تحویل دات نت بسازید. یکی از این ویژگیها در فایل Performance.cs پیادهسازی میشود و دسترسی پویا (یعنی زمان اجرا) را به ویژگیهای هر شی امکانپذیر میسازد – سریعتر از آنچه از System.Reflection.Type دریافت میکنید . CodexMicroORM از این ویژگی در چندین مکان استفاده می کند، یکی توانایی ساخت اشیاء ترکیبی است : "POCO" موجود شما (اشیاء C# قدیمی) با ویژگی های اضافی اختیاری .
انگیزه
اگر بخواهیم به صورت پویا مقادیر ویژگی را از اشیا در زمان اجرا دریافت کنیم، چند رویکرد با سربار عملکرد متفاوت وجود دارد. این دانش عمومی است که بسیاری از روشها در System.Reflection اگرچه قدرتمند و آسان برای استفاده هستند، معمولا آهسته هستند . جایگزینی که تقریباً 10 سال پیش توسط Jon Skeet شرح داده شد هنوز معتبر است: ما میتوانیم نمایندگان(delegates)(delegates)ی ایجاد کنیم که متدهای «Property get» (و تنظیم) را بپیچند - و این نمایندگان(delegates)(delegates) را در حافظه پنهان ذخیره کنیم. این رویکرد در CodexMicroORM استفاده میشود، جایی که میتوانیم از روشهای توسعه سریع زیر در هر «شی» استفاده کنیم:
- FastGetValue
- FastSetValue
- FastPropertyReadable
- FastPropertyReadableWithValue
- FastPropertyWriteable
- FastGetAllProperties
اگر برنامه، سایت یا سرویسی که نیاز به عملکرد فوقالعاده بالایی دارد نمیسازیم، بازتاب سنتی ممکن است "به اندازه کافی خوب" باشد - اما درک میزان تفاوتهای عملکردی نیز خوب است.
عملکرد گزینه های جایگزین
بیایید پیاده سازی و عملکرد چند رویکرد را با استفاده از مهار تست مقایسه کنیم:
- class Person {
- public string Name {
- get;
- set;
- }
- public int ? Age {
- get;
- set;
- }
- }
- static void Main(string[] args) {
- int iterations = 5000000;
- Person p = new Person() {
- Name = "Frank", Age = 30
- };
- int ? age = 0;
- Stopwatch sw = new Stopwatch();
- sw.Start();
- for (int i = 1; i <= iterations; ++i) {
- age += p.Age;
- }
- sw.Stop();
- Console.WriteLine($ "{sw.Elapsed.TotalMilliseconds * 1000.0 / iterations} microseconds per iteration (direct)");
- sw.Restart();
- Func < object, int ? > fn1 = (object o) => ((Person) o).Age;
- for (int i = 1; i <= iterations; ++i) {
- age += fn1(p);
- }
- sw.Stop();
- Console.WriteLine($ "{sw.Elapsed.TotalMilliseconds * 1000.0 / iterations} microseconds per iteration (strongly-typed delegate)");
- sw.Restart();
- Func < object, object > fn2 = (object o) => ((Person) o).Age;
- for (int i = 1; i <= iterations; ++i) {
- age += (int ? ) fn2(p);
- }
- sw.Stop();
- Console.WriteLine($ "{sw.Elapsed.TotalMilliseconds * 1000.0 / iterations} microseconds per iteration (delegate with boxing)");
- sw.Restart();
- for (int i = 1; i <= iterations; ++i) {
- age += (int ? ) p.GetType().GetProperty("Age").GetGetMethod().Invoke(p, new object[] {});
- }
- sw.Stop();
- Console.WriteLine($ "{sw.Elapsed.TotalMilliseconds * 1000.0 / iterations} microseconds per iteration (reflection)");
- sw.Restart();
- for (int i = 1; i <= iterations; ++i) {
- age += (int ? ) p.GetValueReflection("Age");
- }
- sw.Stop();
- Console.WriteLine($ "{sw.Elapsed.TotalMilliseconds * 1000.0 / iterations} microseconds per iteration (cached reflection)");
- sw.Restart();
- for (int i = 1; i <= iterations; ++i) {
- age += (int ? ) p.FastGetValue("Age");
- }
- sw.Stop();
- Console.WriteLine($ "{sw.Elapsed.TotalMilliseconds * 1000.0 / iterations} microseconds per iteration (CEF)");
- Console.ReadLine();
- }
در لپتاپم، حدود 12 نانوثانیه برای دسترسی مستقیم به ویژگی Age میبینم، که یک خط پایه برای دسترسی با تایپ قوی است. اگر به جای آن از یک نماینده با تایپ قوی استفاده کنم - جایی که نوع بازگشت را می دانیم - سرعت نزدیک است: 18 ns. مشکل این مورد در «موقعیتهای عمومی» است که در آن شما انواع هر ویژگی را از قبل نمیدانید، یک نوع نماینده مناسبتر که میتوانیم (برای ذخیرهسازی حافظه پنهان) استفاده کنیم، «Func<object, object>» است. ما توانستیمدر این مورد جریمه عملکردی برای بوکس پرداخت کنید، اما این تا حد زیادی برای انعطاف به دست آمده ضروری است. سوال این است که "آیا پنالتی بوکس به اندازه پنالتی بازتاب است؟" در مهار تست من، 170 ns است. به جای آزمایش با ویژگی Name، عملکرد بسیار بهتر است: اساساً، همان نماینده با تایپ قوی است، زیرا هیچ بوکسی با رشته مورد نیاز نیست. این نقطه پایه ما برای بهترین چیزی است که میتوانیم با استفاده از بستهبندی نماینده به دست آوریم.
با استفاده از System.Reflection (GetProperty و متدهای مرتبط)، 482 ns دریافت می کنم. این کاملاً 40 برابر کندتر از دسترسی مستقیم است و همان چیزی است که اکثر مردم بدون در نظر گرفتن یک جایگزین به دست می آورند. رقم نهایی از روش FastGetValue CodexMicroORM بدست می آید. این 312 ns طول می کشد: بهتر از System.Reflection تا 35% اما نه به خوبی دسترسی با استفاده از یک نماینده در دست.
این ممکن است پایان داستان باشد، با یک نماینده که بازتاب را تقریباً سه برابر می کند - اما به دست آوردن نماینده مورد نظر بسیار پرهزینه است. اولین مجموعه از دستورات برای ایجاد نماینده از بازتاب استاندارد استفاده می کند، با این ایده که می خواهید نماینده حاصل را برای درخواست های بعدی برای همان ویژگی در همان نوع ذخیره کنید. این منجر به 140+ ns سربار اضافی برای ذخیره خود می شود.