اشتراک‌ها
JIT compiler آزمایشی جدید دات نت
تیم دات نت در حال تهیه یک JIT compiler جدید است که برای دنیای X64 بهینه سازی شده‌است و همچنین از تکنیک‌های جدید کامپایلر ++VC استفاده می‌کند.
JIT compiler آزمایشی جدید دات نت
نظرات مطالب
امکان تعریف ساده‌تر خواص Immutable در C# 9.0 با معرفی ویژگی خواص Init-Only
یک نکته‌ی تکمیلی: امکان استفاده‌ی از records و init-only properties در نگارش‌های پیشین دات نت

فقط اگر از NET 5x. به عنوان Target Framework استفاده کنید، زبان تنظیم شده‌ی پیش‌فرض آن سی‌شارپ 9 است. اما اگر برای مثال بخواهید این زبان را در پروژه‌های مبتنی بر net standard 2.1 که زبان پیش‌فرض آن‌ها ‍C# 8.0 است نیز فعال کنید، اینکار با بازنویسی صریح شماره نگارش زبان آن در فایل csproj ممکن است:
<LangVersion>9.0</LangVersion>
اما پس از آن به یک مشکل برخواهید خورد: برای کار با records و init-only properties، نوع جدید IsExternalInit باید به کامپایلر معرفی شود که این نوع، جزئی از NET 5x SDK. هست. بنابراین برای سایر SDKها، نیاز است قطعه کد زیر را به صورت دستی به پروژه‌ی خود اضافه کنید:
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.ComponentModel;

namespace System.Runtime.CompilerServices
{
   /// <summary>
   /// Reserved to be used by the compiler for tracking metadata.
   /// This class should not be used by developers in source code.
   /// </summary>
   [EditorBrowsable(EditorBrowsableState.Never)]
   internal static class IsExternalInit
   {
   }
}
مطالب
وادار کردن IIS به استفاده از ASP.Net 3.5

همانطور که مطلع هستید در تنظیمات یک دایرکتوری مجازی در IIS6 یا 5، حتی پس از نصب دات نت فریم ورک سه و نیم، گزینه انتخاب نگارش 3.5 ظاهر نمی‌شود و همان تنظیمات ASP.Net 2.0 کافی است (شکل زیر) (دات نت 3 و سه و نیم را می‌توان بعنوان افزونه‌هایی با مقیاس سازمانی (WF ، WCF و ...) برای دات نت 2 درنظر گرفت).




هنگام استفاده از VS.Net 2008 و تنظیم نوع پروژه به دات نت فریم ورک 3.5 ، به صورت خودکار تنظیمات لازم به وب کانفیگ برنامه جهت استفاده از کامپایلرهای مربوطه نیز اضافه می‌شوند که شاید از نظر دور بمانند.
برای آزمایش این مورد، فرض کنید صفحه زیر را بدون استفاده از code behind و VS.Net ایجاد کرده ایم (جهت آزمایش سریع یک قطعه کد Linq ).

<%@ Page Language="C#" %>

<%@ Import Namespace="System" %>
<%@ Import Namespace="System.Linq" %>

<form id="Form1" method="post" runat="server">
<asp:GridView ID="GridView1" runat="server" />
</form>


<script runat="server">
protected void Page_Load(object sender, EventArgs e)
{
string[] cities = {
"London", "Amsterdam", "San Francisco", "Las Vegas",
"Boston", "Raleigh", "Chicago", "Charlestown",
"Helsinki", "Nice", "Dublin"
};

GridView1.DataSource = from city in cities
where city.Length > 4
orderby city
select city.ToUpper();

GridView1.DataBind();
}
</script>

بلافاصله پس از اجرا با خطای زیر روبرو خواهیم شد.



این قطعه کد چون از قابلیت‌های کامپایلر جدید سی شارپ استفاده می‌کند، با کامپایلر پیش فرض و تنظیم شده دات نت 2 کار نخواهد کرد و باید برای رفع این مشکل، فایل web.config جدیدی را نیز به پوشه برنامه اضافه کنیم:

<?xml version="1.0"?>
<configuration>

<system.codedom>
<compilers>
<compiler language="c#;cs;csharp" extension=".cs" warningLevel="4" type="Microsoft.CSharp.CSharpCodeProvider, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<providerOption name="CompilerVersion" value="v3.5"/>
<providerOption name="WarnAsError" value="false"/>
</compiler>
</compilers>
</system.codedom>

<system.web>
<compilation defaultLanguage="c#">
<assemblies>
<add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
</assemblies>
</compilation>
</system.web>


</configuration>

در اینجا قید اسمبلی System.Core ضروری است و همچنین نگارش کامپایلر نیز به صورت صریح قید شده است تا IIS را وادار کند که از قابلیت‌های جدید دات نت فریم ورک استفاده نماید.

همانطور که ذکر شد اگر از VS.Net 2008 استفاده کنید، هیچ وقت درگیر این مباحث نخواهید شد و همه چیز از پیش تنظیم شده است.

مطالب
اهمیت ارائه‌ی برنامه‌های دات نت به صورت release

یکی از مواردی که بعضی از همکاران هنگام ارائه برنامه‌های خود رعایت نمی‌کنند، تفاوت قائل نشدن بین حالت release و debug در زمان کامپایل پروژه، برای ارائه نهایی است.
هنگام استفاده از حالت release ، گزینه‌های بهینه سازی کامپایلر فعال شده و همچنین debug symbols از اسمبلی نهایی تولید شده حذف می‌گردد (بنابراین حجم اسمبلی نهایی نیز کمتر خواهد شد). لازم به ذکر است در حالت release ، میزان مصرف حافظه برنامه تولید شده نیز کمتر از حالت debug خواهد بود. گاهی از اوقات سرعت اجرای این دو حالت تا چندین برابر در بعضی از الگوریتم‌ها می‌توانند متفاوت باشند.
مطابق مستندات موجود، وجود debug symbols هیچگونه تاثیری بر روی کارآیی یک برنامه دات نت ندارند.
لازم به ذکر است که عمده بهینه‌سازی‌ها در دات نت توسط JIT compiler صورت می‌گیرد (تا 99 درصد) و نه توسط کامپایلر زبان مورد استفاده (به همین جهت است که عده‌ای اعتقاد دارند در نهایت و هنگام اجرا تفاوتی مابین زبان‌های مختلف دات نت وجود نخواهد داشت). بر روی JIT compiler نیز می‌توان تاثیر گذاشت و نحوه عملکرد آنرا تغییر داد (حتی بر روی یک اسمبلی کامپایل شده). برای مثال یک فایل ini کنار اسمبلی پروژه خود ایجاد کنید (xyz.ini که در اینجا xyz.exe نام فایل اجرایی برنامه است). محتویات این فایل می‌تواند به صورت زیر باشد:

[.NET Framework Debugging Control]
GenerateTrackingInfo=1
AllowOptimize=0

در اینجا می‌توان بهینه سازی را فعال و غیر فعال کرد و یا مطابق تنظیمات فوق برنامه را جهت دیباگ آماده نمود. (این روش با اسمبلی‌های ASP.Net کار نمی‌کند)
در دات نت فریم ورک 2 ، TrackingInfo مربوط به JIT compiler همواره تولید خواهد شد اما می‌توان بر روی بهینه سازی نهایی به صورت فوق نیز تاثیرگذار بود.

نکته:
اگر می‌خواهید هنگام مشاهده گزارش خطا، شماره سطر مورد نظر نیز در کدهای شما گوشزد ‌شود فایلهای pdb - program database تولید شده را هم ارائه دهید. حال شاید بخواهید هم برنامه را در حالت release ارائه دهید و هم pdb آن تولید شود، در این حالت باید خط فرمان کامپایل برنامه، با سوئیچ debug:pdbonly/ اجرا شود.
این مورد را در قسمت خواص پروژه، گزینه build و با کلیک بر روی دکمه advanced نیز می‌توان تنظیم نمود. (حالت پیش فرض release در VS.Net است)

خلاصه‌ی کلام: لطفا هنگام ارائه نهایی، گزینه release را از بالای صفحه در VS.Net انتخاب کنید. با تشکر!

مطالب
بهبود کارآیی حلقه‌های foreach در دات نت 7
بالاخره تفاوت کارآیی بین حلقه‌های for و foreach در دات نت 7 برطرف شده‌است که این مورد نیز یکی دیگر از دلایل بهبود کارآیی LINQ در دات نت 7 است. در این مطلب به همراه آزمایشی، این مورد را بررسی خواهیم کرد.


تدارک یک آزمایش برای بررسی کارآیی حلقه‌های for و foreach در دات نت 7

یک برنامه‌ی کنسول جدید را ایجاد کرده و سپس کتابخانه‌ی BenchmarkDotNet را با TargetFramework دات نت 7 به صورت زیر به پروژه اضافه می‌کنیم:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net7.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="BenchmarkDotNet" Version="0.13.4" />
  </ItemGroup>
</Project>
در ادامه به این پروژه، کلاس زیر را اضافه می‌کنیم:
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;

namespace NET7Loops;

[SimpleJob(RuntimeMoniker.Net60)]
[SimpleJob(RuntimeMoniker.Net70)]
[MemoryDiagnoser(false)]
public class Benchmarks
{
    private int[] ItemsArray;
    private List<int> ItemsList;

    [GlobalSetup]
    public void Setup()
    {
        var random = new Random(420);
        var randomItems = Enumerable.Range(0, 1000).Select(_ => random.Next());
        ItemsArray = randomItems.ToArray();
        ItemsList = randomItems.ToList();
    }

    [Benchmark]
    public void For_Array()
    {
        for (var i = 0; i < ItemsArray.Length; i++)
        {
            var item = ItemsArray[i];
        }
    }

    [Benchmark]
    public void For_List()
    {
        for (var i = 0; i < ItemsList.Count; i++)
        {
            var item = ItemsList[i];
        }
    }

    [Benchmark]
    public void ForEach_Array()
    {
        foreach (var item in ItemsArray)
        {
        }
    }

    [Benchmark]
    public void ForEach_List()
    {
        foreach (var item in ItemsList)
        {
        }
    }
}
که توسط دستورات زیر در حالت release اجرا شده و نتایج نهایی را نمایش می‌دهد:
using BenchmarkDotNet.Running;
using NET7Loops;

BenchmarkRunner.Run<Benchmarks>();
توضیحات:

- می‌توان یک پروژه را یکبار بر اساس دات نت 7 و یکبار هم بر اساس دات نت 6 با تغییر target framework آن‌ها کامپایل و اجرا کرد تا بتوان نتایج این دو را با هم مقایسه کرد و یا می‌توان با ذکر [SimpleJob(RuntimeMoniker.Net60)] و همچنین [SimpleJob(RuntimeMoniker.Net70)]، این مورد را به صورت خودکار به BenchmarkDotNet دات نت واگذار کرد.
- در این آزمایش، ابتدا یک آرایه و یک لیست را تهیه می‌کنیم.
- سپس یکبار حلقه‌های for و foreach را بر روی آرایه و همین عملیات را بر روی لیست تهیه شده، تکرار می‌کنیم.

نتایج حاصل به صورت زیر هستند:


همانطور که در نتایج فوق هم مشاهده می‌کنید:
در دات نت 6
- تفاوتی بین کارآیی حلقه‌ها‌ی for و foreach، زمانیکه بر روی یک آرایه اجرا می‌شوند، وجود ندارد.
- اما کارآیی حلقه‌ی foreach نسبت به حلقه‌ی for، زمانیکه بر روی یک لیست اجرا می‌شوند، تقریبا 50 درصد کمتر است.

در دات نت 7
- تفاوتی بین کارآیی حلقه‌ها‌ی for و forach، زمانیکه بر روی یک آرایه اجرا می‌شوند، وجود ندارد. بنابراین از این لحاظ با دات نت 6 تفاوتی ندارد.
- اما کارآیی حلقه‌ی foreach نسبت به حلقه‌ی for، زمانیکه بر روی یک لیست اجرا می‌شود، تقریبا یکسان و قابل چشم‌پوشی است. یعنی در دات نت 7، کارآیی این دو حلقه یکی شده‌است. اما چرا؟


روشی در جهت یافتن یکی بودن سرعت حلقه‌های for و foreach بر اساس خروجی کامپایلر

با مشاهده‌ی نتایج حاصل از BenchmarkDotNet می‌توان به بهبود کارآیی حاصل پی‌برد؛ اما برای مثال چرا زمانیکه از آرایه استفاده می‌شود، حتی در دات نت 6، تفاوتی بین دو حلقه‌ی for و foreach وجود ندارد، اما زمانیکه از لیست‌ها استفاده می‌شود، این کارآیی 50 درصد افت می‌کند؟
برای پاسخ به این سؤال می‌توان از IL Viewer موجود در Rider استفاده کرد که آخرین نگارش آن به همراه نمایش #Low-level C هم هست:

این همان خروجی است که توسط کامپایلر، پیش از تولید کدهای باینری نهایی، تهیه می‌شود. یعنی اگر قصد داشته باشیم تا درک کامپایلر را نسبت به قطعه کدی مشاهده کنیم، می‌توان به این خروجی مراجعه کرد که به صورت زیر است:
// Decompiled with JetBrains decompiler
// Type: NET7Loops.Benchmarks
// Assembly: NET7Loops, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// MVID: E398BEE7-8123-4C55-AF9A-F7D83DDA73F1
// Assembly location: C:\Prog\1401\Net7Tests\NET7Loops\bin\Debug\net7.0\NET7Loops.dll
// Compiler-generated code is shown

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;

namespace NET7Loops
{
  [NullableContext(1)]
  [Nullable(0)]
  [SimpleJob(RuntimeMoniker.Net60, -1, -1, -1, -1, null, false)]
  [SimpleJob(RuntimeMoniker.Net70, -1, -1, -1, -1, null, false)]
  [MemoryDiagnoser(false)]
  public class Benchmarks
  {
    private int[] ItemsArray;
    private List<int> ItemsList;

    [GlobalSetup]
    public void Setup()
    {
      Benchmarks.<>c__DisplayClass2_0 cDisplayClass20 = new Benchmarks.<>c__DisplayClass2_0();
      cDisplayClass20.random = new Random(420);
      IEnumerable<int> source = Enumerable.Range(0, 1000).Select<int, int>(new Func<int, int>((object) cDisplayClass20, __methodptr(<Setup>b__0)));
      this.ItemsArray = source.ToArray<int>();
      this.ItemsList = source.ToList<int>();
    }

    [Benchmark(23, "C:\\Prog\\1401\\Net7Tests\\NET7Loops\\Benchmarks.cs")]
    public void For_Array()
    {
      for (int index = 0; index < this.ItemsArray.Length; ++index)
      {
        int items = this.ItemsArray[index];
      }
    }

    [Benchmark(32, "C:\\Prog\\1401\\Net7Tests\\NET7Loops\\Benchmarks.cs")]
    public void For_List()
    {
      for (int index = 0; index < this.ItemsList.Count; ++index)
      {
        int items = this.ItemsList[index];
      }
    }

    [Benchmark(41, "C:\\Prog\\1401\\Net7Tests\\NET7Loops\\Benchmarks.cs")]
    public void ForEach_Array()
    {
      int[] itemsArray = this.ItemsArray;
      for (int index = 0; index < itemsArray.Length; ++index)
      {
        int num = itemsArray[index];
      }
    }

    [Benchmark(49, "C:\\Prog\\1401\\Net7Tests\\NET7Loops\\Benchmarks.cs")]
    public void ForEach_List()
    {
      List<int>.Enumerator enumerator = this.ItemsList.GetEnumerator();
      try
      {
        while (enumerator.MoveNext())
        {
          int current = enumerator.Current;
        }
      }
      finally
      {
        enumerator.Dispose();
      }
    }

    public Benchmarks()
    {
      base..ctor();
    }

    [CompilerGenerated]
    private sealed class <>c__DisplayClass2_0
    {
      [Nullable(0)]
      public Random random;

      public <>c__DisplayClass2_0()
      {
        base..ctor();
      }

      internal int <Setup>b__0(int _)
      {
        return this.random.Next();
      }
    }
  }
}
در این خروجی بهتر می‌توان مشاهده کرد که چرا در حالت استفاده‌ی از آرایه‌ها، تفاوتی بین حلقه‌های for و foreach نیست؛ چون هر دو به صورت حلقه‌ی for تفسیر می‌شوند:
for (int index = 0; index < this.ItemsArray.Length; ++index)
{
   int items = this.ItemsArray[index];
}
اما زمانیکه به لیست‌ها می‌رسیم، حلقه‌ی foreach به صورت زیر تفسیر می‌شود که بدیهی است نسبت به حلقه‌ی for، کندتر اجرا خواهد شد:
      List<int>.Enumerator enumerator = this.ItemsList.GetEnumerator();
      try
      {
        while (enumerator.MoveNext())
        {
          int current = enumerator.Current;
        }
      }
      finally
      {
        enumerator.Dispose();
      }
اگر این خروجی را برای دات نت 6 و دات نت 7 تهیه کنیم، به یک جواب خواهیم رسید. یعنی از دیدگاه #Low-level C، تفاوتی بین IL دات نت 6 و 7 از این لحاظ وجود ندارد. تفاوتی اصلی در بهبودهای JIT دات نت 7 است که سبب شده، خروجی نهایی حلقه‌‌های foreach با for یکی باشد.