EF Code First #11
Globalization در ASP.NET MVC
<input dir="ltr" class="form-control input-validation-error" type="email" data-val="true" data-val-email="'آدرس ایمیل' is not a valid email address." data-val-remote="این آدرس ایمیل هم اکنون مورد استفادهاست" data-val-remote-url="/Home/ValidateUniqueEmail" data-val-required="'آدرس ایمیل' must not be empty." id="Email" name="Email" >
افزودن آدرس ایمیل به مدل کاربران
به همان مدل قسمت قبل، قصد داریم خاصیت آدرس ایمیل را هم اضافه کنیم:
using System.ComponentModel.DataAnnotations; namespace FluentValidationSample.Models { public class UserModel { [Display(Name = "نام کاربری")] public string Username { get; set; } [Display(Name = "سن")] public int Age { get; set; } [Display(Name = "سابقه کار")] public int Experience { get; set; } [DataType(DataType.EmailAddress)] [Display(Name = "آدرس ایمیل")] public string Email { get; set; } } }
ایجاد سرویسی برای بررسی منحصربفرد بودن آدرس ایمیل
در ادامه قصد داریم سرویسی را ایجاد کنیم که برای مثال با بانک اطلاعاتی ارتباط برقرار کرده (در اینجا جهت سهولت ارائه، از یک آرایه استفاده شدهاست) و مشخص میکند که آیا ایمیل دریافتی پیشتر استفاده شدهاست یا خیر:
using System.Linq; namespace FluentValidationSample.Services { public interface IUsersService { bool IsUniqueEmail(string emailAddress); } public class UsersService : IUsersService { public bool IsUniqueEmail(string emailAddress) { string[] registedEmails = { "email@site.com", "test@gmail.com" }; return !registedEmails.Contains(emailAddress); } } }
namespace FluentValidationSample.Web { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddScoped<IUsersService, UsersService>();
ایجاد اعتبارسنج سمت سرور بررسی منحصربفرد بودن آدرس ایمیل
در ادامه یک PropertyValidator جدید را ایجاد میکنیم تا بتوان توسط آن مقدار ایمیل دریافتی را در سمت سرور، تعیین اعتبار کرد:
using FluentValidation.Validators; using FluentValidationSample.Services; namespace FluentValidationSample.ModelsValidations { public class UniqueEmailValidator : PropertyValidator { private readonly IUsersService _usersService; public UniqueEmailValidator(IUsersService usersService) : base("این آدرس ایمیل هم اکنون مورد استفادهاست") { _usersService = usersService; } protected override bool IsValid(PropertyValidatorContext context) { return context.PropertyValue != null && _usersService.IsUniqueEmail((string)context.PropertyValue); } } }
پس از آن جهت سهولت استفادهی از آن، یک متد الحاقی جدید را نیز به نام UniqueEmail به نحو زیر تعریف میکنیم:
using FluentValidation; using FluentValidationSample.Services; namespace FluentValidationSample.ModelsValidations { public static class CustomValidatorExtensions { public static IRuleBuilderOptions<T, string> UniqueEmail<T>( this IRuleBuilder<T, string> ruleBuilder, IUsersService usersService) { return ruleBuilder.SetValidator(new UniqueEmailValidator(usersService)); } } }
یک نکته: اگر دقت کرده باشید، فضای نام این اعتبارسنج در این قسمت FluentValidationSample.ModelsValidations شدهاست:
علت اینجا است که اعتبارسنج تعریف شده نیاز دارد هم از مدلها استفاده کند و هم از سرویس کاربران. سرویس کاربران هم از مدلها استفاده میکند. به همین جهت اگر تعاریف اعتبارسنجی را داخل پروژهی مدلها قرار دهیم، یک وابستگی حلقوی رخ خواهد داد (وابستگی مدلها به سرویسها و برعکس). بنابراین بهتر است اعتبارسنجها را به یک پروژهی مجزا منتقل کنیم تا از بروز این cyclic dependency جلوگیری شود.
اعمال اعتبارسنجی منحصربفرد بودن ایمیل دریافتی به اعتبارسنج UserModel
پس از تهیهی متد الحاقی UniqueEmail، آنرا به RuleFor مخصوص خاصیت ایمیل اضافه میکنیم. در اینجا نیز تزریق وابستگی سرویس سفارشی IUsersService به سازندهی کلاس اعتبارسنج مجاز است:
using FluentValidation; using FluentValidationSample.Models; using FluentValidationSample.Services; namespace FluentValidationSample.ModelsValidations { public class UserModelValidator : AbstractValidator<UserModel> { public UserModelValidator(IUsersService usersService) { RuleFor(x => x.Username).NotNull(); RuleFor(x => x.Age).NotNull(); RuleFor(x => x.Experience).LowerThan(nameof(UserModel.Age)).NotNull(); RuleFor(x => x.Email).EmailAddress().NotNull().UniqueEmail(usersService); } } }
ایجاد متادیتای مورد نیاز جهت unobtrusive java script validation در سمت سرور
در ادامه نیاز است ویژگیهای data-val خاص unobtrusive java script validation را توسط FluentValidation ایجاد کنیم:
using FluentValidation; using FluentValidation.AspNetCore; using FluentValidation.Internal; using FluentValidation.Resources; using FluentValidation.Validators; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; namespace FluentValidationSample.ModelsValidations { public class RemoteClientValidator : ClientValidatorBase { public string RemoteUrl { set; get; } public RemoteClientValidator(PropertyRule rule, IPropertyValidator validator) : base(rule, validator) { } public override void AddValidation(ClientModelValidationContext context) { MergeAttribute(context.Attributes, "data-val", "true"); MergeAttribute(context.Attributes, "data-val-remote", GetErrorMessage(context)); MergeAttribute(context.Attributes, "data-val-remote-url", RemoteUrl); } private string GetErrorMessage(ClientModelValidationContext context) { var formatter = ValidatorOptions.MessageFormatterFactory().AppendPropertyName(Rule.GetDisplayName()); string messageTemplate; try { messageTemplate = Validator.Options.ErrorMessageSource.GetString(null); } catch (FluentValidationMessageFormatException) { messageTemplate = ValidatorOptions.LanguageManager.GetStringForValidator<NotEmptyValidator>(); } return formatter.BuildMessage(messageTemplate); } } }
<input dir="ltr" class="form-control input-validation-error" type="email" data-val="true" data-val-email="'آدرس ایمیل' is not a valid email address." data-val-remote="این آدرس ایمیل هم اکنون مورد استفادهاست" data-val-remote-url="/Home/ValidateUniqueEmail" data-val-required="'آدرس ایمیل' must not be empty." id="Email" name="Email" >
افزودن اعتبارسنجهای تعریف شده به تنظیمات برنامه
پس از تعریف UniqueEmailValidator و RemoteClientValidator، روش افزودن آنها به تنظیمات FluentValidation به صورت زیر است:
namespace FluentValidationSample.Web { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddScoped<IUsersService, UsersService>(); services.AddControllersWithViews().AddFluentValidation( fv => { fv.RegisterValidatorsFromAssembly(Assembly.GetExecutingAssembly()); fv.RegisterValidatorsFromAssemblyContaining<RegisterModelValidator>(); fv.RunDefaultMvcValidationAfterFluentValidationExecutes = false; fv.ConfigureClientsideValidation(clientSideValidation => { // ... clientSideValidation.Add( validatorType: typeof(UniqueEmailValidator), factory: (context, rule, validator) => new RemoteClientValidator(rule, validator) { RemoteUrl = "/Home/ValidateUniqueEmail" }); }); } ); }
namespace FluentValidationSample.Web.Controllers { public class HomeController : Controller { private readonly IUsersService _usersService; public HomeController(IUsersService usersService) { _usersService = usersService; } // ... public IActionResult ValidateUniqueEmail(string email) { return Ok(_usersService.IsUniqueEmail(email)); } } }
تعریف کدهای جاوا اسکریپتی مورد نیاز
پیش از هرکاری، اسکریپتهای فایل layout برنامه باید چنین تعریفی را داشته باشند:
<script src="~/lib/jquery/dist/jquery.min.js"></script> <script src="~/lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script> <script src="~/lib/jquery-validation/dist/jquery.validate.js"></script> <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.js"></script> <script src="~/js/site.js" asp-append-version="true"></script>
آزمایش برنامه
View این قسمت نیز همانند قسمت قبل است که فقط یک آدرس ایمیل به آن اضافه شدهاست:
برای آزمایش آن اگر برای مثال یکی از آدرسهای ایمیل از پیش تعریف شدهی در متد IsUniqueEmail سرویس کاربران را وارد کنیم، با خطای اعتبارسنجی سمت کلاینت فوق روبرو خواهیم شد.
کدهای کامل این سری را تا این قسمت از اینجا میتوانید دریافت کنید: FluentValidationSample-part04.zip
قبلا در همین وبسایت در مورد یکسانسازی حروف "ی" و "ک" مطلبی بیان شده است. تمرکز آن مطلب بر روی اعمال تغییرات، قبل از ذخیره در دیتابیس با استفاده از EF است. به عبارتی، متنهایی که توسط مدیر سایت یا هر کاربر دیگری برای ذخیره شدن در دیتابیس وارد شده است. اما در صورتی که کاربری در جستجوی خود از حروف "ی" و "ک" عربی استفاده کند چه میشود؟ در اینجا میخواهیم روشی را پیادهسازی کنیم که عمومی بوده و بتواند هر دوی این حالات را پوشش دهد.
مسئله این است که بایستی تمام ورودیهای کاربران سایت که از نوع رشته هستند چک و در صورت نیاز اصلاح شوند. بهترین روشی که به ذهن من میرسد این است که در فرایند Model Binding این عمل انجام بگیرد. با تعریف یک Model Binder برای نوع رشته میتوانیم عمل یکدستسازی را به صورت عمومی در تمام وبسایت اعمال کنیم.
در MVC یک Model Binder پیشفرض داریم به نام DefaultModelBinder . ما هم از همین کلاس استفاده میکنیم تا تمام کارها را برای ما انجام دهد. تنها بایستی در متد BindModel آن کد موردنظر خود را اضافه کنیم و سپس اجازه دهیم بقیه فرایند به شکل عادی ادامه پیدا کند.
کلاسی به نام StringModelBinder اضافه کرده و کلاس DefaultModelBinder را به عنوان کلاس پایه آن تعریف میکنیم. سپس متد BindModel آنرا override کرده، کد مربوط به یکدستسازی را اضافه میکنیم :
public class StringModelBinder : DefaultModelBinder { public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { object value = base.BindModel(controllerContext, bindingContext); if (value == null) { return value; } return value.ToString().Replace((char)1610, (char)1740).Replace((char)1603, (char)1705); } }
ابتدا مقدار به دست آمده توسط متد پیشفرض را میگیریم. سپس در صورت نیاز یکسانسازی را انجام میدهیم.
برای فعال کردن این Model Binder بایستی آنرا در رویداد Application_start برنامه، برای نوع رشته به ModelBinderهای برنامه اضافه کنیم :
ModelBinders.Binders.Add(typeof(string), new StringModelBinder());
معماری لایه بندی نرم افزار #3
شما الان اومدی یک بحث انتزاعی کلی رو شروع کردید. بله. اگر ORM رو کنار بگذارید مثلا میرسید به ADO.NET (یک نمونه که خیلیها در این سایت حداقل یکبار باهاش کار کردن). این افراد پیش از اینکه این مباحث مطرح باشن برای خودشون لایه DAL داشتند و تمام جزئیات ADO.NET رو کپسوله کرده بودن در اون. حالا با اومدن ORMها این لایه DAL کنار رفته چون خود ORM هست که کپسوله کننده ADO.NET است. همینها هم یک لایه دیگر داشتند به نام BLL که از لایه DAL استفاده میکرد برای پیاده سازی منطق تجاری برنامه. این لایه الان اسمش شده لایه سرویس.
یعنی تمام مواردی رو که عنوان کردید در مورد ADO.NET صدق میکنه. یکی اسمش رو میذاره DAL شما اسمش رو گذشتید Repository. ولی این مباحث ربطی به یک ORM تمام عیار که کپسوله کننده ADO.NET است ندارد.
- ترکیب چند SP در لایه مخزن انجام نمیشه. چیزی رو که عنوان کردید یعنی پیاده سازی منطق تجاری و این مورد باید در لایه سرویس باشه. اگر از ADO.NET استفاده میشه، میتونیم با استفاده از DAL جزئیات دسترسی به SP رو مخفی و سادهتر کنیم با کدی یک دستتر در تمام برنامه. اگر از EF استفاده میکنیم، باز همین ساده سازی در طی فراخوانی فقط یک متد انجام شده. بنابراین بهتر است وضعیت و سطح لایهای رو که داریم باهاش کار میکنیم خوب بررسی و درک کنیم.
- میتونید در عمل در بین پروژههای سورس باز و معتبر موجود فقط یک نمونه رو به من ارائه بدید که در اون از 2 مورد ORM مختلف همزمان استفاده شده باشه؟ این مورد یعنی سؤ مدیریت. یعنی پراکندگی و انجام کاری بسیار مشکل مثلا یک نمونه: ORMها لایهای دارند به نام سطح اول کش که مثلا در EF اسمش هست Trackig API. این لایه فقط در حین کار با Context همون ORM کار میکنه. اگر دو مورد رو با هم مخلوط کنید، قابل استفاده نیست، ترکیب پذیر نیستند. از این دست باز هم هست مثلا در مورد نحوه تولید پروکسیهایی که برای lazy loading تولید میکنند و خیلی از مسایل دیگری از این دست. ضمن اینکه مدیریت چند Context فقط در یک لایه خودش یعنی نقض اصل تک مسئولیتی کلاسها.
چند نکته کاربردی درباره Entity Framework
در متد Updateایی که نوشتید، قسمت Find حتما اتفاق میافته. چون Tracking خاموش هست (مطابق تنظیماتی که عنوان کردید)، بنابراین Find چیزی رو از کشی که وجود نداره نمیتونه دریافت کنه و میره سراغ دیتابیس. ماخذ :
The Find method on DbSet uses the primary key value to attempt to find an entity tracked by the context. If the entity is not found in the context then a query will be sent to the database to find the entity there. Null is returned if the entity is not found in the context or in the database.
قسمت Find متد Update شما در حالت detached اضافی است. یعنی اگر میدونید که این Id در دیتابیس وجود داره نیازی به Findاش نیست. فقط State اون رو تغییر بدید کار میکنه.
در حالت نه آنچنان Detached ! (دریافت یک لیست از Context ایی که ردیابی نداره)
با خاموش کردن Tracking حتما نیاز خواهید داشت تا متد context.ChangeTracker.DetectChanges رو هم پیش از ذخیره سازی یک لیست دریافت شده از بانک اطلاعاتی فراخوانی کنید. وگرنه چون این اطلاعات ردیابی نمیشوند، هر تغییری در آنها، وضعیت Unchanged رو خواهد داشت و نه Detached. بنابراین SaveChanges عمل نمیکنه؛ مگر اینکه DetectChanges فراخوانی بشه.
سؤال: این سربار که میگن چقدر هست؟ ارزشش رو داره که راسا خاموشش کنیم؟ یا بهتره فقط برای گزارشگیری این کار رو انجام بدیم؟
یک آزمایش:
using System; using System.Collections.Generic; using System.Data; using System.Data.Entity; using System.Data.Entity.Migrations; using System.Diagnostics; using System.Linq; namespace EF_General.Models.Ex21 { public abstract class BaseEntity { public int Id { set; get; } } public class Factor : BaseEntity { public int TotalPrice { set; get; } } public class MyContext : DbContext { public DbSet<Factor> Factors { get; set; } public MyContext() { } public MyContext(bool withTracking) { if (withTracking) return; this.Configuration.ProxyCreationEnabled = false; this.Configuration.LazyLoadingEnabled = false; this.Configuration.AutoDetectChangesEnabled = false; } public void CustomUpdate<T>(T entity) where T : BaseEntity { if (entity == null) throw new ArgumentException("Cannot add a null entity."); var entry = this.Entry<T>(entity); if (entry.State != EntityState.Detached) return; /*var set = this.Set<T>(); // اینها اضافی است //متد فایند اگر اینجا باشه حتما به بانک اطلاعاتی رجوع میکنه در حالت منقطع از زمینه و در یک حلقه به روز رسانی کارآیی مطلوبی نخواهد داشت T attachedEntity = set.Find(entity.Id); if (attachedEntity != null) { var attachedEntry = this.Entry(attachedEntity); attachedEntry.CurrentValues.SetValues(entity); } else {*/ entry.State = EntityState.Modified; //} } } public class Configuration : DbMigrationsConfiguration<MyContext> { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } protected override void Seed(MyContext context) { if (!context.Factors.Any()) { for (int i = 0; i < 20; i++) { context.Factors.Add(new Factor { TotalPrice = i }); } } base.Seed(context); } } public class Performance { public TimeSpan ListDisabledTracking { set; get; } public TimeSpan ListNormal { set; get; } public TimeSpan DetachedEntityDisabledTracking { set; get; } public TimeSpan DetachedEntityNormal { set; get; } } public static class Test { public static void RunTests() { startDb(); var results = new List<Performance>(); var runs = 20; for (int i = 0; i < runs; i++) { Console.WriteLine("\nRun {0}", i + 1); var tsListDisabledTracking = PerformanceHelper.RunActionMeasurePerformance(() => updateListTotalPriceDisabledTracking()); var tsListNormal = PerformanceHelper.RunActionMeasurePerformance(() => updateListTotalPriceNormal()); var tsDetachedEntityDisabledTracking = PerformanceHelper.RunActionMeasurePerformance(() => updateDetachedEntityTotalPriceDisabledTracking()); var tsDetachedEntityNormal = PerformanceHelper.RunActionMeasurePerformance(() => updateDetachedEntityTotalPriceNormal()); results.Add(new Performance { ListDisabledTracking = tsListDisabledTracking, ListNormal = tsListNormal, DetachedEntityDisabledTracking = tsDetachedEntityDisabledTracking, DetachedEntityNormal = tsDetachedEntityNormal }); } var detachedEntityDisabledTrackingAvg = results.Average(x => x.DetachedEntityDisabledTracking.TotalMilliseconds); Console.WriteLine("detachedEntityDisabledTrackingAvg: {0} ms.", detachedEntityDisabledTrackingAvg); var detachedEntityNormalAvg = results.Average(x => x.DetachedEntityNormal.TotalMilliseconds); Console.WriteLine("detachedEntityNormalAvg: {0} ms.", detachedEntityNormalAvg); var listDisabledTrackingAvg = results.Average(x => x.ListDisabledTracking.TotalMilliseconds); Console.WriteLine("listDisabledTrackingAvg: {0} ms.", listDisabledTrackingAvg); var listNormalAvg = results.Average(x => x.ListNormal.TotalMilliseconds); Console.WriteLine("listNormalAvg: {0} ms.", listNormalAvg); } private static void updateDetachedEntityTotalPriceNormal() { using (var context = new MyContext(withTracking: true)) { var detachedEntity = new Factor { Id = 1, TotalPrice = 10 }; var attachedEntity = context.Factors.Find(detachedEntity.Id); if (attachedEntity != null) { attachedEntity.TotalPrice = 100; context.SaveChanges(); } } } private static void updateDetachedEntityTotalPriceDisabledTracking() { using (var context = new MyContext(withTracking: false)) { var detachedEntity = new Factor { Id = 2, TotalPrice = 10 }; detachedEntity.TotalPrice = 200; context.CustomUpdate(detachedEntity); // custom update with change tracking disabled. context.SaveChanges(); } } private static void updateListTotalPriceNormal() { using (var context = new MyContext(withTracking: true)) { foreach (var item in context.Factors) { item.TotalPrice += 10; // normal update with change tracking enabled. } context.SaveChanges(); } } private static void updateListTotalPriceDisabledTracking() { using (var context = new MyContext(withTracking: false)) { foreach (var item in context.Factors) { item.TotalPrice += 10; //نیازی به این دو سطر نیست //context.ChangeTracker.DetectChanges(); // هربار باید محاسبه صورت گیرد در غیراینصورت وضعیت تغییر نیافته گزارش میشود //context.CustomUpdate(item); // custom update with change tracking disabled. } context.ChangeTracker.DetectChanges(); // در غیراینصورت وضعیت تغییر نیافته گزارش میشود context.SaveChanges(); } } private static void startDb() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>()); // Forces initialization of database on model changes. using (var context = new MyContext()) { context.Database.Initialize(force: true); } } } public class PerformanceHelper { public static TimeSpan RunActionMeasurePerformance(Action action) { var stopwatch = new Stopwatch(); stopwatch.Start(); action(); stopwatch.Stop(); return stopwatch.Elapsed; } } }
detachedEntityDisabledTrackingAvg: 22.32089 ms. detachedEntityNormalAvg: 54.546815 ms. listDisabledTrackingAvg: 413.615445 ms. listNormalAvg: 393.194625 ms.
در حالت کار با لیستی از اشیاء دریافت شده از بانک اطلاعاتی، به روز رسانی حالت متصل به Context سریعتر است.
معرفی تعدادی آنالیز کنندهی کد که به عنوان افزونهی کامپایلر #C قابل استفاده هستند
Microsoft.CodeAnalysis.NetAnalyzers
این افزونه جزئی از SDK دات نت 5 است و نیازی به نصب مجزا را ندارد. البته اگر میخواهید نگارشهای جدیدتر آنرا پیش از یکی شدن با SDKهای بعدی مورد آزمایش قرار دهید، میتوان آنرا به صورت صریحی نیز به کامپایلر معرفی کرد. این افزونهی جایگزین FxCop است و پس از ارائهی آن، FxCop را منسوخ شده اعلام کردند.
Meziantou.Analyzer
یکسری نکات بهبود کیفیت کدها که توسط Meziantou در طی سالهای متمادی جمع آوری شدهاند، تبدیل به افزونهی فوق شدهاند.
Microsoft.VisualStudio.Threading.Analyzers
این افزونه نکاتی را در مورد مشکلات Threading موجود در کدها، گوشزد میکند.
Microsoft.CodeAnalysis.BannedApiAnalyzers
با استفاده از این افزونه میتوان استفادهی از یکسری کدها را ممنوع کرد. برای مثال استفادهی از System.DateTimeOffset.DateTime، در سراسر کدها ممنوع شده و استفادهی از System.DateTimeOffset.UtcDateTime پیشنهاد شود.
AsyncFixer و Asyncify
این دو افزونه، مشکلات متداول در حین کار با کدهای async را گوشزد میکنند.
ClrHeapAllocationAnalyzer
این افزونه مکانهایی از کد را مشخص میکنند که در آنها تخصیص حافظه صورت گرفتهاست. کاهش این مکانها میتواند به بالا رفتن کارآیی برنامه کمک کنند.
SonarAnalyzer.CSharp
مجموعهی معروف Sonar، که تعداد قابل ملاحظهای بررسی کنندهی کد را به پروژهی شما اضافه میکنند.
روش معرفی سراسری افزونههای فوق به تمام پروژههای یک Solution
میتوان تنظیمات زیر را به یک تک پروژه اعمال کرد که برای اینکار نیاز است فایل csproj آنرا ویرایش نمود و یا میتوان یک تک فایل ویژه را به نام Directory.Build.props ایجاد کرد و آنرا به صورت زیر تکمیل نمود. محل قرارگیری این فایل، در ریشهی Solution و در کنار فایل sln میباشد.
<Project> <PropertyGroup> <AnalysisLevel>latest</AnalysisLevel> <AnalysisMode>AllEnabledByDefault</AnalysisMode> <CodeAnalysisTreatWarningsAsErrors>true</CodeAnalysisTreatWarningsAsErrors> <EnableNETAnalyzers>true</EnableNETAnalyzers> <EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild> <Nullable>enable</Nullable> <TreatWarningsAsErrors>true</TreatWarningsAsErrors> <RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild> <RunAnalyzersDuringLiveAnalysis>true</RunAnalyzersDuringLiveAnalysis> <!-- CA2007: Consider calling ConfigureAwait on the awaited task MA0004: Use Task.ConfigureAwait(false) as the current SynchronizationContext is not needed CA1056: Change the type of property 'Url' from 'string' to 'System.Uri' CA1054: Change the type of parameter of the method to allow a Uri to be passed as a 'System.Uri' object CA1055: Change the return type of method from 'string' to 'System.Uri' --> <NoWarn>$(NoWarn);CA2007;CA1056;CA1054;CA1055;MA0004</NoWarn> <NoError>$(NoError);CA2007;CA1056;CA1054;CA1055;MA0004</NoError> <Deterministic>true</Deterministic> <Features>strict</Features> <ReportAnalyzer>true</ReportAnalyzer> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.1"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Meziantou.Analyzer" Version="1.0.639"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="16.8.55"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="AsyncFixer" Version="1.3.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="Asyncify" Version="0.9.7"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="ClrHeapAllocationAnalyzer" Version="3.0.0"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> <PackageReference Include="SonarAnalyzer.CSharp" Version="8.16.0.25740"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> </PackageReference> </ItemGroup> <ItemGroup> <AdditionalFiles Include="$(MSBuildThisFileDirectory)BannedSymbols.txt" Link="Properties/BannedSymbols.txt" /> </ItemGroup> </Project>
- در تنظیمات فوق، مواردی مانند AnalysisLevel، در مطلب «کامپایلر C# 9.0، خطاها و اخطارهای بیشتری را نمایش میدهد» پیشتر بررسی شدهاند.
- در اینجا Nullable به true تنظیم شدهاست. اگر قرار است یک پروژهی جدید را شروع کنید، بهتر است این ویژگی را نیز فعال کنید. بسیاری از APIهای دات نت 5 جهت مشخص سازی خروجی نال و یا غیرنال آنها، بازنویسی و تکمیل شدهاند و بدون استفاده از این ویژگی، بسیاری از راهنماییهای ارزندهی دات نت 5 را از دست خواهید داد. اساسا بدون فعالسازی این ویژگی، از قابلیتهای #C مدرن استفاده نمیکنید.
- وجود این PackageReference ها، به معنای بالا رفتن حجم نهایی قابل ارائهی پروژه نیست؛ چون به صورت PrivateAssets و analyzers تعریف شدهاند و فقط در حین پروسهی کامپایل، جهت ارائهی راهنماییهای بیشتر، تاثیرگذار خواهند بود.
- این تنظیمات طوری چیده شدهاند که تا حد ممکن «درد آور» باشند! برای اینکار CodeAnalysisTreatWarningsAsErrors و TreatWarningsAsErrors به true تظیم شدهاند تا حتی اخطارها نیز به صورت خطای کامپایلر گزارش شوند؛ تا مجبور به رفع آنها شویم.
- در اینجا فایل BannedSymbols.txt را نیز مشاهده میکنید که مرتبط است به BannedApiAnalyzers. میتوان در کنار فایل Directory.Build.props، فایل جدید BannedSymbols.txt را با این محتوا ایجاد کرد:
# https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md P:System.DateTime.Now;Use System.DateTime.UtcNow instead P:System.DateTimeOffset.Now;Use System.DateTimeOffset.UtcNow instead P:System.DateTimeOffset.DateTime;Use System.DateTimeOffset.UtcDateTime instead
روش کاهش تعداد خطاهای نمایش داده شده
اگر از فایل Directory.Build.props فوق استفاده کرده و یکبار دستور dotnet restore را جهت بازیابی وابستگیهای آن اجرا کنید، با تعداد خطاهایی که در IDE خود مشاهده خواهید کرد، شگفتزده خواهید شد! به همین جهت برای کنترل آنها میتوان فایل جدید editorconfig. را به نحو زیر در کنار فایل Directory.Build.props ایجاد و تکمیل کرد:
[*.cs] # MA0026 : Complete the task dotnet_diagnostic.MA0026.severity = suggestion # CA1308: In method 'urlToLower', replace the call to 'ToLowerInvariant' with 'ToUpperInvariant' (CA1308) dotnet_diagnostic.CA1308.severity = suggestion # CA1040: Avoid empty interfaces dotnet_diagnostic.CA1040.severity = suggestion # CA1829 Use the "Count" property instead of Enumerable.Count() dotnet_diagnostic.CA1829.severity = suggestion # Use 'Count' property here instead. dotnet_diagnostic.S2971.severity = suggestion # S1135 : Complete the task dotnet_diagnostic.S1135.severity = suggestion # S2479: Replace the control character at position 7 by its escape sequence dotnet_diagnostic.S2479.severity = suggestion # CA2007: Consider calling ConfigureAwait on the awaited task dotnet_diagnostic.CA2007.severity = none # MA0004: Use Task.ConfigureAwait(false) as the current SynchronizationContext is not needed dotnet_diagnostic.MA0004.severity = none # CA1056: Change the type of property 'Url' from 'string' to 'System.Uri' dotnet_diagnostic.CA1056.severity = suggestion # CA1054: Change the type of parameter of the method to allow a Uri to be passed as a 'System.Uri' object dotnet_diagnostic.CA1054.severity = suggestion # CA1055: Change the return type of method from 'string' to 'System.Uri' dotnet_diagnostic.CA1055.severity = suggestion # S4457: Split this method into two, one handling parameters check and the other handling the asynchronous code. dotnet_diagnostic.S4457.severity = none # AsyncFixer01: Unnecessary async/await usage dotnet_diagnostic.AsyncFixer01.severity = suggestion # AsyncFixer02: Long-running or blocking operations inside an async method dotnet_diagnostic.AsyncFixer02.severity = error # VSTHRD103: Call async methods when in an async method dotnet_diagnostic.VSTHRD103.severity = error # AsyncFixer03: Fire & forget async void methods dotnet_diagnostic.AsyncFixer03.severity = error # VSTHRD100: Avoid async void methods dotnet_diagnostic.VSTHRD100.severity = error # VSTHRD101: Avoid unsupported async delegates dotnet_diagnostic.VSTHRD101.severity = error # VSTHRD107: Await Task within using expression dotnet_diagnostic.VSTHRD107.severity = error # AsyncFixer04: Fire & forget async call inside a using block dotnet_diagnostic.AsyncFixer04.severity = error # VSTHRD110: Observe result of async calls dotnet_diagnostic.VSTHRD110.severity = error # VSTHRD002: Avoid problematic synchronous waits dotnet_diagnostic.VSTHRD002.severity = suggestion # MA0045: Do not use blocking call (make method async) dotnet_diagnostic.MA0045.severity = suggestion # AsyncifyInvocation: Use Task Async dotnet_diagnostic.AsyncifyInvocation.severity = error # AsyncifyVariable: Use Task Async dotnet_diagnostic.AsyncifyVariable.severity = error # VSTHRD111: Use ConfigureAwait(bool) dotnet_diagnostic.VSTHRD111.severity = none # MA0022: Return Task.FromResult instead of returning null dotnet_diagnostic.MA0022.severity = error # VSTHRD114: Avoid returning a null Task dotnet_diagnostic.VSTHRD114.severity = error # VSTHRD200: Use "Async" suffix for async methods dotnet_diagnostic.VSTHRD200.severity = suggestion # MA0040: Specify a cancellation token dotnet_diagnostic.MA0032.severity = suggestion # MA0040: Flow the cancellation token when available dotnet_diagnostic.MA0040.severity = suggestion # MA0079: Use a cancellation token using .WithCancellation() dotnet_diagnostic.MA0079.severity = suggestion # MA0080: Use a cancellation token using .WithCancellation() dotnet_diagnostic.MA0080.severity = error #AsyncFixer05: Downcasting from a nested task to an outer task. dotnet_diagnostic.AsyncFixer05.severity = error # ClrHeapAllocationAnalyzer ---------------------------------------------------- # HAA0301: Closure Allocation Source dotnet_diagnostic.HAA0301.severity = suggestion # HAA0601: Value type to reference type conversion causing boxing allocation dotnet_diagnostic.HAA0601.severity = suggestion # HAA0302: Display class allocation to capture closure dotnet_diagnostic.HAA0302.severity = suggestion # HAA0101: Array allocation for params parameter dotnet_diagnostic.HAA0101.severity = suggestion # HAA0603: Delegate allocation from a method group dotnet_diagnostic.HAA0603.severity = suggestion # HAA0602: Delegate on struct instance caused a boxing allocation dotnet_diagnostic.HAA0602.severity = suggestion # HAA0401: Possible allocation of reference type enumerator dotnet_diagnostic.HAA0401.severity = silent # HAA0303: Lambda or anonymous method in a generic method allocates a delegate instance dotnet_diagnostic.HAA0303.severity = silent # HAA0102: Non-overridden virtual method call on value type dotnet_diagnostic.HAA0102.severity = silent # HAA0502: Explicit new reference type allocation dotnet_diagnostic.HAA0502.severity = none # HAA0505: Initializer reference type allocation dotnet_diagnostic.HAA0505.severity = silent
dotnet_diagnostic.CA1308.severity = suggestion
یک نکته: در ویندوز نمیتوانید یک فایل تنها پسوند دار را به صورت معمولی در windows explorer ایجاد کنید. نام این فایل را به صورت .editorconfig. با دو نقطهی ابتدایی و انتهایی وارد کنید. خود ویندوز نقطهی پایانی را حذف میکند.
روش صرفنظر کردن از یک خطا، تنها در یک قسمت از کد
فرض کنید نمیخواهید خطای CA1052 را تبدیل به یک suggestion سراسری کنید و فقط میخواهید که در قطعهی خاصی از کدهای خود، آنرا خاموش کنید. به همین جهت بجای اضافه کردن آن به فایل editorconfig.، باید از ویژگی SuppressMessage به صورت زیر استفاده نمائید:
[SuppressMessage("Microsoft.Usage", "CA1052:Type 'Program' is a static holder type but is neither static nor NotInheritable", Justification = "We need it for our integration tests this way.")] [SuppressMessage("Microsoft.Usage", "RCS1102:Type 'Program' is a static holder type but is neither static nor NotInheritable", Justification = "We need it for our integration tests this way.")] [SuppressMessage("Microsoft.Usage", "S1118:Type 'Program' is a static holder type but is neither static nor NotInheritable", Justification = "We need it for our integration tests this way.")] public class Program { }
در صورتی که ویژوال استودیوی شما دارای این ورژن و آپدیت نبود، میتوانید چارچوب دات نت 4.6.1 را جداگانه در سیستم خود نصب نمایید. توجه داشته باشید که برای استفاده از چارچوب دات نت در ویژوال استودیو باید نسخههای DevPack یا DeveloperPack را نصب نمایید (دریافت دات نت 4.6.1 نسخه مخصوص استفاده در ویژال استودیو).
در پروژه ایجاد شده فایلی به نام Program.cs و در آن کلاس Program وجود دارد. در این کلاس تابع شروع کننده برنامه یعنی Main وجود دارد و برنامه از این تابع شروع خواهد شد.
نمایی از فایلهای پروژه
در تابع شروع کننده برنامه ابتدا وضعیت پشتیبانی از SIMD را چک میکنیم. این کار را همانطور که قبلا در مقاله پیشنیاز توضیح داده شده است با استفاده از خاصیت Vector.IsHardwareAccelerated بررسی میکنیم. اگر مقدار آن برابر با False باشد به معنای عدم پشتیبانی میباشد و با بررسی این موضوع در اول برنامه، در صورت عدم پشتیبانی از SIMD به اجرای ادامهی برنامه خاتمه میدهیم.
پس از بررسی وضعیت پشتیبانی از SIMD ، تابعی را که در فایل Utilities.cs نوشته شده است، فراخوانی میکنیم. این تابع به بررسی وضعیت تعداد رجیسترهای SIMD و وضعیت انواع نوعهای دادهای در SIMD میپردازد. اگر هر نوع دادهای از SIMD پشتیبانی کند (که بستگی به نوع پردازنده شما دارد) اندازه هر نوع دادهای را در SIMD چاپ میکند و در صورت عدم پشتیبانی هر نوع دادهای از SIMD مقدار «عدم پشتیبانی SIMD از آن نوع دادهای» چاپ خواهد شد.
using System.Numerics; using static System.Console; namespace TestSIMD { class Program { private const int ArraySize = 7680 * 4320; static void Main(string[] args) { // بررسی وضعیت پشتیبانی از SIMD if (!Vector.IsHardwareAccelerated) { WriteLine("Hardware acceleration not supported."); WriteLine(); return; // عدم پشتیبانی و خاتمه برنامه } WriteLine("Hardware acceleration is supported"); // اعلام پشتیبانی از SIMD WriteLine(); // بررسی وضعیت نوعهای داده ای در مشخصات سخت افزاری SIMD Utilities.PrintHardwareSpecificSimdEffectiveness(); //به منظور عدم خروج از برنامه و دیدن نتایج آزمایش WriteLine("Press any key to exit"); ReadKey(); } } }
- فایل IntSimdProcessor.cs
- در این فایل کلاسی به نام IntSimdProcessor قرار دارد که شامل 6 تابع میباشد و این تابعها با نوع دادهای صحیح یا همان Integer کار میکنند. نام کلاس هم به همین خاطر نام گذاری شده است.
- این 6 تابع در کل 3 عملیات را شامل عملیاتهای زیر انجام میدهند. یکبار در حالت معمولی و یکبار با استفاده از توابع SIMD این کار را انجام میدهند:
- پیدا کردن بزرگترین و کوچکترین عدد در آرایه
- جمع عناصر دو آرایه با هم با استفاده از یک آرایه کمکی که نتیجه در آرایه کمکی ریخته میشود
- جمع عناصر دو آرایه بدون استفاده از آرایه کمکی که مجموع در آرایه اول ریخته میشود
- در بالای هر تابع در این فایل توضیحات لازم دربارهی فعالیت آن تابع ذکر شده است.
- فایل FloatSimdProcessor.cs
- در این فایل کلاسی با نام FloatSimdProcessor قرار دارد که همانطور که از نام کلاس پیداست، توابعی برای کار بر روی اعداد از نوع دادهای float در آن نوشته شدهاند.
- در این کلاس هم 6 تابع برای انجام 3 عملیات زیر نوشته شده است که به ازای هر عملیات دو تابع یکی در حالت معمولی و یکی در حالت SIMD نوشته شده است.
- جمع دو آرایه با استفاده از یک آرایه کمکی - مجموع در آرایه کمکی ریخته میشود
- جمع دو آرایه اول ورودی - مجموع در آرایه سوم ریخته میشود
- جمع دو آرایه بدون استفاده از آرایه کمکی - مجموع در آرایه اول ریخته میشود
- در آزمایشات نوشته شده در کلاس PerformanceTests تنها از عملیات آخری استفاده شده است و از دو عملیات اول استفاده نشده است که در صورت تمایل میتوانید از دیگر عملیاتها نیز استفاده کنید.
- در بالای هر تابع در این فایل توضیحات لازم دربارهی فعالیت آن تابع نیز ذکر شده است.
- فایل UShortSimdProcessor.cs
- در این فایل کلاسی با نام UShortSimdProcessor قرار دارد و همانطور که از نام کلاس پیداست، توابعی برای کار بر روی اعداد از نوع دادهای ushort یا همان اعداد صحیح کوچک بدون علامت نوشته شدهاند.
- در این کلاس 12 تابع برای انجام 6 عملیات زیر نوشته شدهاست که به ازای هر عملیات، دو تابع یکی در حالت معمولی و یکی در حالت SIMD نوشته شده است.
- جمع دو آرایه اول ورودی که مجموع در آرایه سوم ریخته میشود
- جمع دو آرایه بدون استفاده از آرایه کمکی که مجموع در آرایه اول ریخته میشود
- بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح کوچک بدون علامت
- جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی
- جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی بدون بررسی سرریز (Overflow)
- محاسبه میانگین و بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح کوچک بدون علامت
- در بالای هر تابع در این فایل توضیحات لازم دربارهی فعالیت آن تابع ذکر شده است.
public static void TestIntArrayAdditionFunctions(int testSetSize) { WriteLine(); Write("Testing int array addition, generating test data..."); var intsOne = GetRandomIntArray(testSetSize); //تولید آرایه عددی به صورت تصادفی var intsTwo = GetRandomIntArray(testSetSize); WriteLine($" done, testing...");// پایان تولید آرایهها و شروع پردازش var naiveTimesMs = new List<long>(); // تعریف لیستی برای ریختن زمان پاسخ دهی در حالت ساده و معمولی var hwTimesMs = new List<long>(); // تعریف لیستی برای ریختن زمان پاسخ دهی در حالت SIMD و سخت افزاری for (var i = 0; i < 3; i++) { // ایجاد حلقه برای تکرار محاسبات برای اندازه گیری زمان در حالت تکراری stopwatch.Restart();//شروع ثبت زمان var result = IntSimdProcessor.NaiveSumFunc(intsOne, intsTwo);//اجرای تابع جمع دو آرایه var naiveTimeMs = stopwatch.ElapsedMilliseconds;//ثبت زمان naiveTimesMs.Add(naiveTimeMs);//افزودن زمان ثبت شده به لیست زمانهای ساده و معمول WriteLine($"Naive analysis took: {naiveTimeMs}ms (last value = {result.Last()})."); stopwatch.Restart();//شروع ثبت زمان result = IntSimdProcessor.HWAcceleratedSumFunc(intsOne, intsTwo);//اجرای تابع جمع دو آرایه در حالت سخت افزاری var hwTimeMs = stopwatch.ElapsedMilliseconds;//ثبت زمان hwTimesMs.Add(hwTimeMs);//افزودن زمان به لیست زمانهای سخت افزاری WriteLine($"Hareware accelerated analysis took: {hwTimeMs}ms (last value = {result.Last()})."); }//پایان حلقه و چاپ نتایج WriteLine("Int array addition:"); WriteLine($"Naive method average time: {naiveTimesMs.Average():.##}"); WriteLine($"HW accelerated method average time: {hwTimesMs.Average():.##}"); WriteLine($"Hardware speedup: {naiveTimesMs.Average() / hwTimesMs.Average():P}%"); }
نام تابع ذکر شده نشان دهنده آزمایش بر روی آرایه اعداد صحیح یا همان Integer میباشد که شامل یک پارامتر ورودی از نوع عدد صحیح میباشد. این پارامتر ورودی نشان دهنده اندازه هر آرایهای میباشد که قرار است تولید شود.
TestIntArrayAdditionFunctions(int testSetSize)
در قدم اول این تابع، باید آرایهها را تولید کنیم که کد آن به صورت زیر است.
Write("Testing int array addition, generating test data..."); var intsOne = GetRandomIntArray(testSetSize); var intsTwo = GetRandomIntArray(testSetSize); WriteLine($" done, testing...");
ابتدا در خروجی چاپ میکنیم که در حال ایجاد دادههای مربوط به آزمایش هستیم و سپس با استفاده از تابع GetRandomIntArray آرایهای را ایجاد میکنیم و در متغیرهای مربوطه میریزیم. این تابع دارای یک پارامتر ورودی از نوع عدد صحیح است که آرایهای را به طول پارامتر ورودی تولید میکند. این تابع در فایل Utilities.cs قرار دارد.
در پایان تولید آرایهها، اتمام تولید و ایجاد آرایهها را با چاپ در خروجی اعلام میکنیم.
سپس با معرفی دو لیست زیر میتوانیم زمانهای اجرا را در آنها بریزیم و در پایان، تابع میانگین این زمانها را محاسبه و چاپ کنیم. لیست اول برای نگهداری زمانهای اجرای عملیات در حالت معمولی و لیست دوم برای نگهداری زمانهای اجرای عملیات در حالت SIMD میباشد.
var naiveTimesMs = new List<long>(); var hwTimesMs = new List<long>();
سپس با ایجاد حلقه ای از 0 تا 3 که در کل 3 مرتبه اجرا میشود عملیات را تکرار و زمان آن را ثبت میکنیم.
for (var i = 0; i < 3; i++)
درون حلقه یک عملیات را در دوحالت معمولی یا ساده و SIMD اجرا میکنیم. قبل از اجرای عملیات اول ابتدا stopwatch را ریست میکنیم. با این کار زمان صفر شده و شروع به اندازه گیری میکند. سپس عملیات مربوط به جمع دو آرایه را در حالت معمولی که در فایل IntSimdProcessor.cs قرار دارد، فراخوانی میکنیم. پس از اجرای این عملیات مقدار stopwatch را به میلی ثانیه در یک متغیر ذخیره میکنیم و این مقدار را به لیست زمانهای اجرای معمولی اضافه میکنیم. در نهایت نتیجه زمان اجرا را در خروجی چاپ میکنیم.
stopwatch.Restart(); var result = IntSimdProcessor.NaiveSumFunc(intsOne, intsTwo); var naiveTimeMs = stopwatch.ElapsedMilliseconds; naiveTimesMs.Add(naiveTimeMs); WriteLine($"Naive analysis took: {naiveTimeMs}ms (last value = {result.Last()}).");
پس از اجرای عملیات در حالت ساده یا معمولی، حال نوبت همان عملیات در حالت SIMD میباشد. دوباره stopwatch را ریست میکنیم و عملیات در SIMD را اجرا کرده و بعد از آن مقدار stopwatch را درون متغیری میریزیم و آن را به لیست زمانهای اجرای عملیات در SIMD اضافه میکنیم و در نهایت نتیجه زمان اجرا را در خروجی چاپ میکنیم.
stopwatch.Restart(); result = IntSimdProcessor.HWAcceleratedSumFunc(intsOne, intsTwo); var hwTimeMs = stopwatch.ElapsedMilliseconds; hwTimesMs.Add(hwTimeMs); WriteLine($"Hareware accelerated analysis took: {hwTimeMs}ms (last value = {result.Last()}).");
پس از اجرای حلقه، حال نوبت به نمایش نتیجه میانگین زمانها در خروجی است. ابتدا میانگین زمانهای اجرا در حالت ساده یا معمولی را که به میلی ثانیه است را در خروجی چاپ میکنیم. بعد از آن میانگین زمانهای اجرا در حالت SIMD را در خروجی چاپ میکنیم و در آخر سرعت زمان اجرا در حالت SIMD را نسبت به حالت معمولی به درصد چاپ میکنیم.
WriteLine($"Naive method average time: {naiveTimesMs.Average():.##}"); WriteLine($"HW accelerated method average time: {hwTimesMs.Average():.##}"); WriteLine($"Hardware speedup: {naiveTimesMs.Average() / hwTimesMs.Average():P}%");
در این مقاله تنها به توضیحی در مورد این آزمایش اکتفا میکنیم. لازم به ذکر است که دیگر آزمایشها نیز دقیقا ساختاری مشابه این آزمایش را دارند و تنها عملیات اجرا در آنها متفاوت است. در کلاس PerformanceTests توضیحات لازم مربوط به هر آزمایش و تابع داده شده است و میتوانید با مراجعه به کد برنامه آنها را مورد بررسی قرار دهید.
برای اجرای تمامی آزمایشها، کلیه توابع نوشته شده در کلاس PerformanceTests را در کلاس Program و در تابع Main که تابع شروع کننده برنامه میباشد، پس از بررسی وضعیت نوعهای دادهای قرار میدهیم.
تصویر مربوط به اجرای کامل برنامه را میتوانید مشاهده میکنید.
این جدول بر اساس یک بار اجرای برنامه در سیستم من ترسیم شده است و اجرای برنامه در سیستمهای مختلف خروجیهای متفاوتی را دارد. لازم به ذکر است که اندازه آرایهها بسیار بزرگ است و این نتایج با آرایههایی به طول بیش از هزاران هزار عنصر میباشد.
زمانها در جدول به میلی ثانیه میباشد.
ردیف | عملیات | دور اول | دور دوم | دور سوم | میانگین حالت ساده | میانگین حالت SIMD | |||
درحالت ساده | درحالت SIMD | درحالت ساده | درحالت SIMD | درحالت ساده | درحالت SIMD | ||||
1 | جمع دو آرایه با استفاده از یک آرایه کمکی در اعداد صحیح | 157 | 131 | 128 | 131 | 128 | 138 | 137.67 | 133.33 |
2 | جمع دو آرایه بدون استفاده از آرایه کمکی در اعداد float | 122 | 133 | 99 | 99 | 99 | 93 | 106.67 | 108.33 |
3 | جمع دو آرایه بدون استفاده از آرایه کمکی در اعداد صحیح | 83 | 73 | 86 | 88 | 78 | 81 | 82.33 | 80.67 |
4 | جمع دو آرایه اول ورودی - مجموع در آرایه سوم ریخته میشود - در اعداد صحیح کوچک بدون علامت | 58 | 63 | 50 | 48 | 58 | 46 | 55.33 | 52.33 |
5 | جمع دو آرایه بدون استفاده از آرایه کمکی در اعداد صحیح کوچک بدون علامت | 55 | 40 | 53 | 36 | 53 | 46 | 53.67 | 40.67 |
6 | بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح | 91 | 36 | 91 | 39 | 90.67 | 38 | 90.66 | 38 |
7 | بدست آوردن کمترین و بیشترین مقدار در یک آرایه اعداد صحیح کوچک بدون علامت | 90 | 20 | 89 | 19 | 88 | 18 | 89 | 19 |
8 | جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی | 33 | 309 | 32 | 263 | 31 | 291 | 32 | 287.67 |
9 | جمع عناصر آرایه ورودی و ذخیره مجموع آنها در یک متغیر کمکی بدون بررسی سرریز | 30 | 13 | 29 | 13 | 30 | 12 | 29.67 | 12.67 |
10 | محاسبه میانگین و بدست آوردن کمترین و بیشترین مقدار در آرایه اعداد صحیح کوچک بدون علامت | 89 | 50 | 90 | 51 | 90 | 49 | 89.57 | 50 |