کنترل DatePicker شمسی مخصوص Silverlight 4
با تشکر از زحمتی که کشیدید :)
استفاده در WPF هم ممکن هست؟
آموزش WPF
گرفتن گزارش از کلیه گریدهای موجود در صفحه
مروری بر روشهای موجود
همواره روشهای مختلفی برای پیاده سازی یک ایده در دنیای نرم افزار وجود دارد که هر روش را میتوان بر حسب نیاز مورد استفاده قرار داد. در برنامههای مبتنی بر WPF معمولا از دو روش عمده برای این منظور استفاده میشود:
1-استفاده از فایلهای resx
در این روش که برای Win App نیز استفاده میشود، اطلاعات مورد نیاز برای هر زبان به شکل جدول هایی دارای کلید و مقدار در داخل یک فایل .resx نگهداری میشود و در زمان اجرای برنامه بر اساس انتخاب کاربر اطلاعات زبان مورد نظر از داخل فایل resx خوانده شده و نمایش داده میشود. یکی از ضعف هایی که این روش در عین ساده بودن دارد این است که همه اطلاعات مورد نیاز داخل assembly اصلی برنامه قرار میگیرد و امکان افزودن زبانهای جدید بدون تغییر دادن برنامه اصلی ممکن نخواهد بود.
2-استفاده از فایلهای csv که به فایلهای dll تبدیل میشوند
در این روش با استفاده از ابزارهای موجود در کامپایلر WPF برای هر کنترل یک property به نام Uid ایجاد شده و مقدار دهی میشود. سپس با ابزار دیگری ( که جزو ابزارهای کامپایلر محسوب نمیشود ) از فایل csproj پروژه یک خروجی اکسل با فرمت csv ایجاد میشود که شامل Uidهای کنترلها و مقادیر آنها است. پس از ترجمه متون مورد نظر به زبان مقصد با کمک ابزار دیگری فایل اکسل مورد نظر به یک net assembly تبدیل میشود و داخل پوشه ای با نام culture استاندارد ذخیره میشود. ( مثلا برای زبان فارسی نام پوشه fa-IR خواهد بود ). زمانی که برنامه اجرا میشود بر اساس culture ای که در سیستم عامل انتخاب شده است و در صورتی که برای آن culture فایل dll ای موجود باشد، زبان مربوط به آن culture را load خواهد کرد. با وجود این که این روش مشکل روش قبلی را ندارد و بیشتر با ویژگیهای WPF سازگار است اما پروسه ای طولانی برای انجام کارها دارد و به ازای هر تغییری باید کل مراحل هر بار تکرار شوند. همچنین مشکلاتی در نمایش برخی زبانها ( از جمله فارسی ) در این روش مشاهده شده است.
روش سوم!
روش سوم اما کاملا بر پایه WPF و در اصطلاح WPF-Native میباشد. ایده از آنجا ناشی شده است که برای ایجاد skin در برنامههای WPF استفاده میشود. در ایجاد برنامههای Skin-Based به این شیوه عمل میشود که skinهای مورد نظر به صورت style هایی در داخل ResourceDictionary ها قرار میگیرند. سپس آن ResourceDictionary به شکل dll کامپایل میشود. در برنامه اصلی نیز همه کنترلها style هایشان را به شکل dynamic resource از داخل یک ResourceDictionary مشخص شده load میکنند. حال کافی است برای تغییر skin فعلی، ResourceDictionary مورد نظر از dll مشخص load شود و ResourceDictionary ای که در حال حاضر در برنامه از آن استفاده میشود با ResourceDictionary ای که load شده جایگزین شود. کنترلها مقادیر جدید را از ResourceDictionary جدید به شکل کاملا خودکار دریافت خواهند کرد.
به سادگی میتوان از این روش برای تغییر زبان برنامه نیز استفاده کرد با این تفاوت که این بار، به جای Style ها، Stringهای زبانهای مختلف را درون resourceها نگهداری خواهیم کرد.
یک مثال ساده
در این قسمت نحوه پیاده سازی این روش با ایجاد یک نمونه برنامه ساده که دارای دو زبان انگلیسی و فارسی خواهد بود آموزش داده میشود.
ابتدا یک پروژه WPF Application در Visual Studio 2010 ایجاد کنید. در MainWindow سه کنترل Button قرار دهید و یک ComboBox که قرار است زبانهای موجود را نمایش دهد و با انتخاب یک زبان، نوشتههای درون Buttonها متناسب با آن تغییر خواهند کرد.
توجه داشته باشید که برای Buttonها نباید به صورت مستقیم مقداری به Content شان داده شود. زیرا مقدار مورد نظر از داخل ResourceDictionary که خواهیم ساخت به شکل dynamic گرفته خواهد شد. پس در این مرحله یک ResourceDictionary به پروژه اضافه کرده و در آن resource هایی به شکل string ایجاد میکنیم. هر resource دارای یک Key میباشد که بر اساس آن، Button مورد نظر، مقدار آن Resource را load خواهد کرد. فایل ResourceDictionary را
Culture_en-US.xaml نامگذاری کنید و مقادیر مورد نظر را به آن اضافه نمایید.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib"> <system:String x:Key="button1">Hello!</system:String> <system:String x:Key="button2">How Are You?</system:String> <system:String x:Key="button3">Are You OK?</system:String> </ResourceDictionary>
دقت کنید که namespace ای که کلاس string در آن قرار دارد به فایل xaml اضافه شده است و پیشوند system به آن نسبت داده شده است.
با افزودن یک ResourceDictionary به پروژه، آن ResourceDictionary به MergedDictionary کلاس App اضافه میشود. بنابراین فایل App.xaml به شکل زیر خواهد بود:
<Application x:Class="BeRMOoDA.WPF.LocalizationSample.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="MainWindow.xaml"> <Application.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Culture_en-US.xaml"/> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </Application.Resources> </Application>
برای اینکه بتوانیم محتوای Buttonهای موجود را به صورت داینامیک و در زمان اجرای برنامه، از داخل Resourceها بگیریم، از DynamicResource استفاده میکنیم.
<Button Content="{DynamicResource ResourceKey=button1}" /> <Button Content="{DynamicResource ResourceKey=button2}" /> <Button Content="{DynamicResource ResourceKey=button3}" />
بسیار خوب! اکنون باید شروع به ایجاد یک ResourceDictionary برای زبان فارسی کنیم و آن را به صورت یک فایل dll کامپایل نماییم.
برای این کار یک پروژه جدید در قسمت WPF از نوع User control ایجاد میکنیم و نام آن را Culture_fa-IR_Farsi قرار میدهیم. لطفا شیوه نامگذاری را رعایت کنید چرا که در ادامه به آن نیاز خواهیم داشت.
پس از ایجاد پروژه فایل UserControl1.xaml را از پروژه حذف کنید و یک ResourceDictionary با نام Culture_fa-IR.xaml اضافه کنید. محتوای آن را پاک کنید و محتوای فایل Culture_en-US.xaml را از پروژه قبلی به صورت کامل در فایل جدید کپی کنید. دو فایل باید ساختار کاملا یکسانی از نظر key برای Resourceهای موجود داشته باشند. حالا زمان ترجمه فرا رسیده است! رشتههای دلخواه را ترجمه کنید و پروژه را build نمایید.
پس از ترجمه فایل Culture_fa-IR.xaml به شکل زیر خواهد بود:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib"> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Culture_fa-IR_Farsi.xaml"/> </ResourceDictionary.MergedDictionaries> <system:String x:Key="button1">سلام!</system:String> <system:String x:Key="button2">حالت چطوره؟</system:String> <system:String x:Key="button3">خوبی؟</system:String> </ResourceDictionary>
در ادامه میخواهیم راهکاری ارئه دهیم تا بتوان فایلهای dll مربوط به زبانها را در زمان اجرای برنامه اصلی، load کرده و نام زبانها را در داخل ComboBox ای که داریم نشان دهیم. سپس با انتخاب هر زبان در ComboBox، محتوای Buttonها بر اساس زبان انتخاب شده تغییر کند.
برای سهولت کار، نام فایلها را به گونه ای انتخاب کردیم که بتوانیم سادهتر به این هدف برسیم. نام هر فایل از سه بخش تشکیل شده است:
پوشه ای با نام Languages در کنار فایل اجرایی برنامه اصلی ایجاد کنید و فایل Culture_fa-IR_Farsi.dll را درون آن کپی کنید. تصمیم داریم همه dllهای مربوط به زبانها را داخل این پوشه قرار دهیم تا مدیریت آنها سادهتر شود.
برای مدیریت بهتر فایلهای مربوط به زبانها یک کلاس با نام CultureAssemblyModel خواهیم ساخت که هر instance از آن نشانگر یک فایل زبان خواهد بود. یک کلاس با این نام به پروژه اضافه کنید و propertyهای زیر را در آن تعریف نمایید:
public class CultureAssemblyModel { //the text will be displayed to user as language name (like Farsi) public string DisplayText { get; set; } //name of .dll file (like Culture_fa-IR_Farsi.dll) public string Name { get; set; } //standar notation of this culture (like fa-IR) public string Culture { get; set; } //name of resource dictionary file inside the loaded .dll (like Culture_fa-IR.xaml) public string XamlFileName { get; set; } }
برای خواندن لیست cultureهای موجود، لیستی از CultureAssmeblyModelها ایجاد کرده و با استفاده از متد LoadCultureAssmeblies، آن را پر میکنیم.
//will keep information about loaded assemblies public List<CultureAssemblyModel> CultureAssemblies { get; set; } //loads assmeblies in languages folder and adds their info to list void LoadCultureAssemblies() { //we should be sure that list is empty before adding info (do u want to add some cultures more than one? of course u dont!) CultureAssemblies.Clear(); //creating a directory represents applications directory\languages DirectoryInfo dir = new DirectoryInfo(System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\languages"); //getting all .dll files in the language folder and its sub dirs. (who knows? maybe someone keeps each culture file in a seperate folder!) var assemblies = dir.GetFiles("*.dll", SearchOption.AllDirectories); //for each found .dll we will create a model and set its properties and then add to list for (int i = 0; i < assemblies.Count(); i++) {
string name = assemblies[i].Name;
CultureAssemblyModel model = new CultureAssemblyModel() { DisplayText = name.Split('.', '_')[2], Culture = name.Split('.', '_')[1], Name = name , XamlFileName =name.Substring(0, name.LastIndexOf(".")) + ".xaml" }; CultureAssemblies.Add(model); } }
comboboxLanguages.ItemsSource = CultureAssemblies;
<ComboBox HorizontalAlignment="Left" Margin="10" VerticalAlignment="Top" MinWidth="100" Name="comboboxLanguages"> <ComboBox.ItemTemplate> <DataTemplate> <Label Content="{Binding DisplayText}"/> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox>
در مرحله بعد، قرار است متدی بنویسیم که اطلاعات زبان انتخاب شده را گرفته و با جابجایی ResourceDictionary ها، زبان برنامه را تغییر دهیم.
متدی با نام LoadCulture در کلاس App ایجاد میکنیم که یک CultureAssemblyModel به عنوان ورودی دریافت کرده و ResourceDictionary داخل آن را load میکند و آن را با ResourceDictionary فعلی موجود در App.xaml جابجا مینماید.
با این کار، Button هایی که قبلا مقدار Content خود را از Resourceهای موجود دریافت میکردند، اکنون از Resourceهای جابجا شده خواهند گرفت و به این ترتیب زبان انتخاب شده بر روی برنامه اعمال میشود.
//loads selected culture public void LoadCulture(CultureAssemblyModel culture) { //creating a FileInfo object represents .dll file of selected cultur FileInfo assemblyFile = new FileInfo("languages\\" + culture.Name); //loading .dll into memory as a .net assembly var assembly = Assembly.LoadFile(assemblyFile.FullName); //getting .dll file name var assemblyName = assemblyFile.Name.Substring(0, assemblyFile.Name.LastIndexOf(".")); //creating string represents structure of a pack uri (something like this: /{myassemblyname;component/myresourcefile.xaml} string packUri = string.Format(@"/{0};component/{1}", assemblyName, culture.XamlFileName); //creating a pack uri Uri uri = new Uri(packUri, UriKind.Relative); //now we have created a pack uri that represents a resource object in loaded assembly //and its time to load that as a resource dictionary (do u remember that we had resource dictionary in culture assemblies? don't u?) var dic = Application.LoadComponent(uri) as ResourceDictionary; dic.Source = uri; //here we will remove current merged dictionaries in our resource dictionary and add recently-loaded resource dictionary as e merged dictionary var mergedDics = this.Resources.MergedDictionaries; if (mergedDics.Count > 0) mergedDics.Clear(); mergedDics.Add(dic); }
void comboboxLanguages_SelectionChanged(object sender, SelectionChangedEventArgs e) { var selectedCulture = (CultureAssemblyModel)comboboxLanguages.SelectedItem; App app = Application.Current as App; app.LoadCulture(selectedCulture); }
کار انجام شد!
از مزیتهای این روش میتوان به WPF-Native بودن، سادگی در پیاده سازی، قابلیت load کردن هر زبان جدیدی در زمان اجرا بدون نیاز به کوچکترین تغییر در برنامه اصلی و همچنین پشتیبانی کامل از نمایش زبانهای مختلف از جمله فارسی اشاره کرد.
آشنایی با WPF قسمت دوم: Layouts بخش اول
The Open XML SDK provides tools for working with Office Word, Excel, and PowerPoint documents. It supports scenarios such as:
- High-performance generation of word-processing documents, spreadsheets, and presentations.
- Document modification, such as adding, updating, and removing content and metadata.
- Search and replace content using regular expressions.
- Splitting up (shredding) a file into multiple files, and combining multiple files into a single file.
- Updating cached data and embedded spreadsheets for charts in Word/PowerPoint.
SET STATISTICS TIME ON
SELECT ProductID, StartDate, EndDate, StandardCost FROM Production.ProductCostHistory WHERE StandardCost < 500.00;
SQL Server parse and compile time: CPU time = 0 ms, elapsed time = 1 ms. (269 row(s) affected) SQL Server Execution Times: CPU time = 1 ms, elapsed time = 2 ms.
-
زمان کامپایل و تجزیه و تحلیل ( parse and compile time) : زمانیکه یک کوئری را برای اجرا به SQL Server ارائه میدهید، SQL Server آنرا از نظر خطای نحوی بررسی مینماید و بهینه ساز یک نقشه بهینه را برای اجرا تولید مینماید. اگر به خروجی نگاه کنید، زمان پردازش ( CPU time) و زمان سپری شده ( elapsed time) را نشان می دهد. منظور از زمان پردازش زمان واقعی صرف شده روی پردازنده میباشد و زمان سپری شده اشاره به زمان تکمیل شدن عملیات کامپایل و تجزیه و تحلیل میباشد. تفاوت بین زمان پردازش و زمان سپری شده ممکن است زمان انتظار در صف برای گرفتن پردازنده و یا انتظار برای تکمیل عملیات IO باشد.
-
زمان اجرا ( Execution Times) : این زمان اشاره به زمان سپری شده برای تکمیل اجرای نقشه کامپایل شده دارد. زمان پردازش اشاره به زمان واقعی صرف شده روی پردازنده دارد و زمان سپری شده نیز مجموع زمان صرف شده تا تکمیل اجرای دستور که شامل زمان انتظار برای تکمیل عملیات IO و زمان صرف شده برای انتقال خروجی به کلاینت میباشد، دارد. زمان پردازش میتواند به عنوان مبنایی برای اندازهگیری کارآیی مورد استفاده قرار بگیرد. این مقدار در اجراهای مختلف تفاوت چندانی با هم ندارند جز اینکه کوئری و یا دادهها تغییر نمایند. توجه نمایید که زمان براساس میلی ثانیه میباشد. زمان سپری شده نیز به فاکتورهایی مانند بارگذاری روی سرور، بارگذاری IO، و پهنای باند بین سرور و کلاینت وابسته میباشد. بنابراین همیشه زمان پردازش به عنوان مبنایی برای اندازهگیری کارایی استفاده میشود .
در صورتی که ویژوال استودیوی شما دارای این ورژن و آپدیت نبود، میتوانید چارچوب دات نت 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 |
یک. قبل از آپلود امکان برش وجود داشته باشد.
دو. سیستم برش قابلیت پشتیبانی از تناسب بین پهنا و ارتفاع را داشته باشد.
سه. قابلیت استفاده خارج از قواعد Ajax را داشته باشد و بتوان ارسال آن را به طور دستی کنترل کرد.
چهار. پشتیبانی برش تصاویر به صورت تاچ برای گوشیهای همراه را نیز دارا باشد.
کتابخانه jcrop کتابخانهای است که این امکانات را برای شما فراهم میکند. این کتابخانه بدین صورت است که در حین برش به شما 4 عدد x1,x2,y1,y2 را داده و شما با ارسال آن به سمت سرور میتوانید بر اساس این اعداد، عکس اصلی را برش بزنید. بدین صورت شما هم عکس اصلی را دارید و هم مختصات برش را دارید و اگر دوست دارید در جاهای مختلف از عکس اصلی برش داشته باشید، بسیار مفید خواهد بود.
مرحله اول:
ابتدا فایل jcrop را دانلود نمایید.
مرحله دوم:
کد Html زیر را به صفحه اضافه کنید:
<div> <!-- upload form --> <!-- hidden crop params --> <input type="hidden" id="x1" name="x1" /> <input type="hidden" id="y1" name="y1" /> <input type="hidden" id="x2" name="x2" /> <input type="hidden" id="y2" name="y2" /> <h2>ابتدا تصویر خود را انتخاب کنید</h2> <div><input type="file" name="postedFileBase" data-buttonText="انتخاب تصویر" id="image_file" onchange="fileSelectHandler()" /></div> <div></div> <div> <h2>قسمتی از تصویر را انتخاب نمایید</h2> <img id="preview" /> <div> <label>حجم فایل </label> <input type="text" id="filesize" name="filesize" /> <label>نوع فایل</label> <input type="text" id="filetype" name="filetype" /> <label>ابعاد فایل</label> <input style="direction: ltr;" type="text" id="filedim" name="filedim" /> </div> </div> </div>
تگ step2 نیز بعد از نمایش موفقیت آمیز تصویر نشان داده میشود که کاربر میتواند در آن تصویر را برش دهد و شامل بخش info نیز میباشد تا بتوان اندازه اصلی تصویر، نوع فایل تصویر Content Type و حجم آن را نمایش داد.
مرحله سوم:
سپس برای استایل دهی کدهای بالا از کد Css زیر استفاده میکنیم:
.bheader { background-color: #DDDDDD; border-radius: 10px 10px 0 0; padding: 10px 0; text-align: center; } .bbody { color: #000; overflow: hidden; padding-bottom: 20px; text-align: center; background: -moz-linear-gradient(#ffffff, #f2f2f2); background: -ms-linear-gradient(#ffffff, #f2f2f2); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f2f2f2)); background: -webkit-linear-gradient(#ffffff, #f2f2f2); background: -o-linear-gradient(#ffffff, #f2f2f2); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2'); -ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f2f2f2')"; background: linear-gradient(#ffffff, #f2f2f2); } .bbody h2, .info, .error { margin: 10px 0; } .step2, .error { display: none; } .error { color: red; } .info { } label { margin: 0 5px; } .roundinput { border: 1px solid #CCCCCC; border-radius: 10px; padding: 4px 8px; text-align: center; width: 150px; } .jcrop-holder { display: inline-block; } input[type=submit] { background: #e3e3e3; border: 1px solid #bbb; border-radius: 3px; -webkit-box-shadow: inset 0 0 1px 1px #f6f6f6; box-shadow: inset 0 0 1px 1px #f6f6f6; color: #333; padding: 8px 0 9px; text-align: center; text-shadow: 0 1px 0 #fff; width: 150px; } input[type=submit]:hover { background: #d9d9d9; -webkit-box-shadow: inset 0 0 1px 1px #eaeaea; box-shadow: inset 0 0 1px 1px #eaeaea; color: #222; cursor: pointer; } input[type=submit]:active { background: #d0d0d0; -webkit-box-shadow: inset 0 0 1px 1px #e3e3e3; box-shadow: inset 0 0 1px 1px #e3e3e3; color: #000; }
مرحله چهارم:
افزودن کد جاوااسکریپتی زیر برای کار کردن با کتابخانه Jcrop میباشد:
function bytesToSize(bytes) { var sizes = ['بایت', 'کیلو بایت', 'مگابایت']; if (bytes == 0) return 'n/a'; var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i]; }; function updateInfo(e) { $('#x1').val(e.x); $('#y1').val(e.y); $('#x2').val(e.x2); $('#y2').val(e.y2); }; var jcrop_api, boundx, boundy; function fileSelectHandler() { var oFile = $('#image_file')[0].files[0]; $('.error').hide(); var rFilter = /^(image\/jpeg|image\/png)$/i; if (!rFilter.test(oFile.type)) { $('.error').html('فقط تصویر معتبر انتخاب نمایید').show(); return; } var oImage = document.getElementById('preview'); var oReader = new FileReader(); oReader.onload = function (e) { oImage.src = e.target.result; oImage.onload = function () { $('.step2').fadeIn(500); var sResultFileSize = bytesToSize(oFile.size); $('#filesize').val(sResultFileSize); $('#filetype').val(oFile.type); $('#filedim').val(oImage.naturalWidth + ' x ' + oImage.naturalHeight); if (typeof jcrop_api != 'undefined') { jcrop_api.destroy(); jcrop_api = null; $('#preview').width(oImage.naturalWidth); $('#preview').height(oImage.naturalHeight); } $('#preview').Jcrop({ aspectRatio: 2, bgFade: true, bgOpacity: .3, onChange: updateInfo, onSelect: updateInfo }, function () { //var bounds = this.getBounds(); //var boundx = bounds[0]; //var boundy = bounds[1]; // Store the Jcrop API in the jcrop_api variable jcrop_api = this; }); }; }; oReader.readAsDataURL(oFile); }
تابع fileSelectHandler
function fileSelectHandler() { var oFile = $('#image_file')[0].files[0]; $('.error').hide(); var rFilter = /^(image\/jpeg|image\/png)$/i; if (!rFilter.test(oFile.type)) { $('.error').html('فقط تصویر معتبر انتخاب نمایید').show(); return; }
در ادامه همین تابع بالا، کدهای زیر را اضافه میکنیم:
var oImage = document.getElementById('preview'); var oReader = new FileReader(); oReader.onload = function (e) { oImage.src = e.target.result; oImage.onload = function () { $('.step2').fadeIn(500); var sResultFileSize = bytesToSize(oFile.size); $('#filesize').val(sResultFileSize); $('#filetype').val(oFile.type); $('#filedim').val(oImage.naturalWidth + ' x ' + oImage.naturalHeight); if (typeof jcrop_api != 'undefined') { jcrop_api.destroy(); jcrop_api = null; $('#preview').width(oImage.naturalWidth); $('#preview').height(oImage.naturalHeight); } $('#preview').Jcrop({ aspectRatio: 2, bgFade: true, bgOpacity: .3, onChange: updateInfo, onSelect: updateInfo, onRelease: clearInfo }, function () { //var bounds = this.getBounds(); //var boundx = bounds[0]; //var boundy = bounds[1]; jcrop_api = this; }); }; }; oReader.readAsDataURL(oFile);
FileReader یکی از توابع موجود در HTML است که مستندات آن در سایت موزیلا موجود است و قابلیت خواندن غیرهمزمان فایلها و اشیا Blob را دارد. در خط آخر به عنوان پارامتر ما فایلی را که در آپلودر خوانده ایم و در مرحله قبل نوع فایل آن را بررسی کردیم، پاس میکنیم و باعث میشود که رویداد Load شیء FileReader صدا زده شود.
در این رویداد ابتدا اطلاعات این فایل را از قبیل سایز و ابعاد و نوع فایل، خوانده و در همان تگ Div که با کلاس info تعیین شده بود، نمایش میدهیم. سپس متغیر jcrop_api را که به صورت global در بالای تابع صدا زدیم، بررسی میکنم که آیا از قبل پر شدهاست یا خیر؟ اگر از قبل پرشدهاست باید شیء Jcrop را که به آن اعمال شده است، نابود و آن را نال کنیم تا برای تصویر جدید آماده شود. این کد زمانی کاربرد دارد که کاربر از تصویر قبلی انصراف دادهاست و تصویر جدیدی را انتخاب نموده است یا اینکه عملیات دارد به صورت ایجکسی پیاده میشود. اگر عملیات نابودی روی این پلاگین صورت نگیرد، برای مرتبه دوم کار نخواهد کرد.
سپس پلاگین جیکوئری Jcrop را بر روی آن اعمال میکنیم. در پرامتر اول یک سری تنظیمات اولیه را انجام میدهیم که در ادامه با آن آشنا میشویم و در پارامتر دوم یک callback را به آن پاس میکنیم تا بعد از آماده شدن پلاگین اجرا شود که در آن شیء جدید ایجاد شده یعنی this را در متغیری به اسم jcrop_api دخیره میکنیم تا در بررسیهای آتی که در بند بالا توضیح داده شد، در دسترس داشته باشیم. همچنین در این تابع شما میتوانید اندازه تصویر انتخابی را نیز داشته باشید.
این پلاگین شامل optionهای متفاوتی در پارامتر اول است که آنها را بررسی میکنیم:
MinSize : شما میتوانید حداقل پهنا و ارتفاعی را برای برش زدن تصویر در نظر بگیرید.
minSize:[40,20]
aspectRatio:1.5
bgOpacity از 0 تا یک مقدار میگیرد و میزان opacity محلهای تاریک را تعیین میکند. همچنین شامل سه رویداد onSelect,onChange,onrelease هم میباشد که به ترتیب در موارد زیر رخ میدهند:
ناحیه مورد نظر انتخاب شد.
ناحیه مورد نظر در حالت انتخاب است و ماوس در حال درگ شدن است و با هر حرکتی ماوس اجرا میگردد.
ناحیه انتخابی از حالت انتخاب خارج شد.
دو رویداد اول یعنی onchange و onSelect را برای به روزسانی فیلدهای مخفی و مختصات استفاده میکنیم:
function updateInfo(e) { $('#x1').val(e.x); $('#y1').val(e.y); $('#x2').val(e.x2); $('#y2').val(e.y2); };
این مختصات از طریق یک پارامتر به آنها پاس میشود. به غیر از این چهار عدد مختصات میتوانید با استفاده از متغیرهای w و h هم اندازه پهنا و ارتفاع محل برش خورده را نیز به دست آورید. هر چند که این اعداد، از تفریق خود مختصات هم به دست میآیند.
یک تابع جزئی دیگر هم در این فایل وجود دارد که حین نمایش اندازه تصویر، واحد نمایش مناسب آن را برای ما انتخاب میکند:
function bytesToSize(bytes) { var sizes = ['بایت', 'کیلو بایت', 'مگابایت']; if (bytes == 0) return 'n/a'; var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i]; };
بعد از اینکه کدهای سمت کلاینت را تمام کردیم لازم است با نحوه برش تصویر در سمت سرور هم آشنا شویم:
public static byte[] Resize(this byte[] byteImageIn, int x1,int y1,int x2,int y2) { ImageConverter ic = new ImageConverter(); Image src = (Image)(ic.ConvertFrom(byteImageIn)); Bitmap target = new Bitmap(x2 - x1, y2 - y1); using (Graphics graphics = Graphics.FromImage(target)) graphics.DrawImage(src, new Rectangle(0, 0, target.Width, target.Height), new Rectangle(x1,y1,x2-x1,y2-y1), GraphicsUnit.Pixel); src = target; using (var ms = new MemoryStream()) { src.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg); return ms.ToArray(); } }
از آنجا که ما تصاویر را در دیتابیس به صورت آرایهای از بایتها ذخیره میکنیم، extension method ذکر شده در بالا تصویر را در حالت آرایهای از بایتها برش میدهد. بدیهی که بسته به نیاز شما کد بالا دست خوش تغییراتی خواهد شد. ابتدا تصویر باینری را به شی Image تبدیل میکنیم و یک شیء Bitmap جدید را به عنوان بوم خالی و به اندازه کادر برش ایجاد میکنیم تا تصویر برش خورده در آن قرار بگیرد و سپس توسط متد DrawImage میخواهیم که تصویر مبدا را با مختصات شیء Rectangle از نقطه 0 و 0 بوم آغاز کرده و تا انتهای آن شروع به ترسیم کند. سپس آن را ذخیره و مجددا در قالب همان آرایهای از بایتها بر میگردانیم.
تنها یک نکته را به خاطر داشته باشید که مقادیر مختصاتی که پلاگین جی کوئری ارسال میکند در قالب اعداد اعشاری هستند و برای ارسال و دریافت آنها در سرور این نکته را به خاطر داشته باشید.