আমি এই প্রশ্নটি খুব আকর্ষণীয় পেয়েছি, বিশেষত যেহেতু আমি async
এডো.নেট এবং ইএফ with এর সাথে সর্বত্র ব্যবহার করছি আমি আশা করি যে কেউ এই প্রশ্নের ব্যাখ্যা দেবে বলে আশাবাদী, তবে তা ঘটেনি। তাই আমি আমার পক্ষ থেকে এই সমস্যাটি পুনরুত্পাদন করার চেষ্টা করেছি। আমি আশা করি আপনারা কেউ কেউ এটি আকর্ষণীয় দেখতে পাবেন।
প্রথম সুসংবাদ: আমি এটি পুনরুত্পাদন করেছি :) এবং পার্থক্যটি বিশাল। 8 একটি ফ্যাক্টর সহ ...
প্রথমে আমি সঙ্গে তার আচরণ কিছু ধারণা ছিল CommandBehavior
যেহেতু, আমি একটি আকর্ষণীয় নিবন্ধ পড়া সম্পর্কে async
Ado সঙ্গে এই বলছে:
"যেহেতু অ-অনুক্রমিক অ্যাক্সেস মোডটিকে পুরো সারির জন্য ডেটা সংরক্ষণ করতে হয়, তাই আপনি যদি সার্ভার থেকে বৃহত্তর কলামটি পড়ছেন (যেমন ভের্বাইনারি (ম্যাক্স), বার্চর (ম্যাক্স), এনভারচার (ম্যাক্স) বা এক্সএমএল )। "
আমি ToList()
কলগুলি CommandBehavior.SequentialAccess
এবং অ্যাসিঙ্ক হওয়ার কথা সন্দেহ করছিলাম CommandBehavior.Default
(অ-অনুক্রমিক, যা সমস্যার কারণ হতে পারে)। সুতরাং আমি EF6 এর উত্স ডাউনলোড করেছি এবং সর্বত্র ব্রেকপয়েন্টগুলি রেখেছি (যেখানে CommandBehavior
যেখানে ব্যবহৃত হয় অবশ্যই)।
ফলাফল: কিছুই না । সমস্ত কলগুলি দিয়ে করা হয়েছে CommandBehavior.Default
.... সুতরাং আমি কী ঘটেছিল তা বোঝার জন্য EF কোডের মধ্যে পদক্ষেপের চেষ্টা করেছি ... এবং .. আউচ ... আমি কখনই এই জাতীয় প্রতিনিধি কোড দেখি না, সবকিছু অলস বলে মনে হচ্ছে ...
তাই আমি কী হয় তা বুঝতে কিছু প্রোফাইল করার চেষ্টা করেছি ...
এবং আমার মনে হয় আমার কিছু আছে ...
এখানে 3500 টি লাইন এবং প্রতিটিতে 256 কেবি এলোমেলো ডেটা সহ আমি বেঞ্চমার্কযুক্ত সারণী তৈরি করার মডেল এখানে varbinary(MAX)
। (মতিন 6.1 - CodeFirst - CodePlex ):
public class TestContext : DbContext
{
public TestContext()
: base(@"Server=(localdb)\\v11.0;Integrated Security=true;Initial Catalog=BENCH") // Local instance
{
}
public DbSet<TestItem> Items { get; set; }
}
public class TestItem
{
public int ID { get; set; }
public string Name { get; set; }
public byte[] BinaryData { get; set; }
}
এবং আমি টেস্ট ডেটা তৈরি করতে কোডটি ব্যবহার করেছি এবং বেঞ্চমার্ক EF।
using (TestContext db = new TestContext())
{
if (!db.Items.Any())
{
foreach (int i in Enumerable.Range(0, 3500)) // Fill 3500 lines
{
byte[] dummyData = new byte[1 << 18]; // with 256 Kbyte
new Random().NextBytes(dummyData);
db.Items.Add(new TestItem() { Name = i.ToString(), BinaryData = dummyData });
}
await db.SaveChangesAsync();
}
}
using (TestContext db = new TestContext()) // EF Warm Up
{
var warmItUp = db.Items.FirstOrDefault();
warmItUp = await db.Items.FirstOrDefaultAsync();
}
Stopwatch watch = new Stopwatch();
using (TestContext db = new TestContext())
{
watch.Start();
var testRegular = db.Items.ToList();
watch.Stop();
Console.WriteLine("non async : " + watch.ElapsedMilliseconds);
}
using (TestContext db = new TestContext())
{
watch.Restart();
var testAsync = await db.Items.ToListAsync();
watch.Stop();
Console.WriteLine("async : " + watch.ElapsedMilliseconds);
}
using (var connection = new SqlConnection(CS))
{
await connection.OpenAsync();
using (var cmd = new SqlCommand("SELECT ID, Name, BinaryData FROM dbo.TestItems", connection))
{
watch.Restart();
List<TestItem> itemsWithAdo = new List<TestItem>();
var reader = await cmd.ExecuteReaderAsync(CommandBehavior.SequentialAccess);
while (await reader.ReadAsync())
{
var item = new TestItem();
item.ID = (int)reader[0];
item.Name = (String)reader[1];
item.BinaryData = (byte[])reader[2];
itemsWithAdo.Add(item);
}
watch.Stop();
Console.WriteLine("ExecuteReaderAsync SequentialAccess : " + watch.ElapsedMilliseconds);
}
}
using (var connection = new SqlConnection(CS))
{
await connection.OpenAsync();
using (var cmd = new SqlCommand("SELECT ID, Name, BinaryData FROM dbo.TestItems", connection))
{
watch.Restart();
List<TestItem> itemsWithAdo = new List<TestItem>();
var reader = await cmd.ExecuteReaderAsync(CommandBehavior.Default);
while (await reader.ReadAsync())
{
var item = new TestItem();
item.ID = (int)reader[0];
item.Name = (String)reader[1];
item.BinaryData = (byte[])reader[2];
itemsWithAdo.Add(item);
}
watch.Stop();
Console.WriteLine("ExecuteReaderAsync Default : " + watch.ElapsedMilliseconds);
}
}
using (var connection = new SqlConnection(CS))
{
await connection.OpenAsync();
using (var cmd = new SqlCommand("SELECT ID, Name, BinaryData FROM dbo.TestItems", connection))
{
watch.Restart();
List<TestItem> itemsWithAdo = new List<TestItem>();
var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);
while (reader.Read())
{
var item = new TestItem();
item.ID = (int)reader[0];
item.Name = (String)reader[1];
item.BinaryData = (byte[])reader[2];
itemsWithAdo.Add(item);
}
watch.Stop();
Console.WriteLine("ExecuteReader SequentialAccess : " + watch.ElapsedMilliseconds);
}
}
using (var connection = new SqlConnection(CS))
{
await connection.OpenAsync();
using (var cmd = new SqlCommand("SELECT ID, Name, BinaryData FROM dbo.TestItems", connection))
{
watch.Restart();
List<TestItem> itemsWithAdo = new List<TestItem>();
var reader = cmd.ExecuteReader(CommandBehavior.Default);
while (reader.Read())
{
var item = new TestItem();
item.ID = (int)reader[0];
item.Name = (String)reader[1];
item.BinaryData = (byte[])reader[2];
itemsWithAdo.Add(item);
}
watch.Stop();
Console.WriteLine("ExecuteReader Default : " + watch.ElapsedMilliseconds);
}
}
নিয়মিত ইএফ কল ( .ToList()
) এর জন্য, প্রোফাইলিংটি "সাধারণ" বলে মনে হচ্ছে এবং এটি পড়া সহজ:
এখানে আমরা স্টপওয়াচটি দিয়ে আমাদের 8.4 সেকেন্ড পেয়েছি (প্রোফাইলিং ধীরে ধীরে পারফটি নিচে নামায়)। আমরা কল পথের সাথে হিটকাউন্ট = 3500ও পাই, যা পরীক্ষার 3500 লাইনের সাথে সামঞ্জস্যপূর্ণ। টিডিএস পার্সার সাইডে, TryReadByteArray()
পদ্ধতিগুলিতে 118 353 টি কল পড়ার পরে জিনিসগুলি আরও খারাপ হতে শুরু করে , যা ছিল বার্ফিং লুপটি। ( byte[]
256kb প্রত্যেকের জন্য গড়ে 33.8 টি কল )
কেসটির জন্য async
, এটি সত্যিই অন্যরকম .... প্রথমে .ToListAsync()
কলটি থ্রেডপুলে নির্ধারিত হয়েছে এবং তারপরে অপেক্ষা করা হচ্ছে। এখানে আশ্চর্যজনক কিছুই। তবে, এখন async
থ্রেডপুলের নরকটি এখানে:
প্রথমত, প্রথম ক্ষেত্রে আমরা পুরো কল পাথ ধরে কেবল 3500 হিট সংখ্যা পেয়েছিলাম, এখানে আমাদের 118 371 রয়েছে Moreover তদুপরি, আপনি স্ক্রিনশুটটি না রেখে সমস্ত সিঙ্ক্রোনাইজেশন কলগুলি কল্পনা করতে হবে ...
দ্বিতীয়ত, প্রথম ক্ষেত্রে, আমাদের TryReadByteArray()
পদ্ধতিটিতে "মাত্র 118 353" কল ছিল , এখানে আমাদের কাছে 050 210 কল রয়েছে! এটি 17 গুণ বেশি ... (বৃহত 1 এমবি অ্যারের সাথে পরীক্ষায় এটি 160 গুণ বেশি)
তাছাড়া এখানে রয়েছে:
- 120 000
Task
দৃষ্টান্ত তৈরি করা হয়েছে
- 727 519
Interlocked
কল
- 290 569
Monitor
কল
ExecutionContext
264 481 ক্যাপচার সহ 98 283 টি উদাহরণ
- 208 733
SpinLock
কল
আমার ধারণা, বাফারিং টিএসডি থেকে ডেটা পড়ার চেষ্টা করে সমান্তরাল টাস্ক সহ একটি অ্যাসিঙ্ক উপায়ে তৈরি করা হয়েছে (এবং ভাল নয়)। কেবল বাইনারি ডেটা পার্স করার জন্য অনেকগুলি টাস্ক তৈরি করা হয়েছে।
প্রাথমিক উপসংহার হিসাবে আমরা বলতে পারি Async দুর্দান্ত, EF6 দুর্দান্ত, তবে EF6 এর বর্তমান বাস্তবায়নে async ব্যবহারের ফলে পারফরম্যান্সের দিক, থ্রেডিং সাইড এবং সিপিইউ পাশ (12% সিপিইউ ব্যবহার) 8 থেকে 10 গুণ বেশি দীর্ঘ কাজের জন্য ToList()
কেস এবং 20% ToListAsync
ক্ষেত্রে ... আমি এটি একটি পুরানো আই 7920 তে চালাই)।
কিছু পরীক্ষা করার সময়, আমি আবার এই নিবন্ধটি সম্পর্কে ভাবছিলাম এবং আমি কিছু মিস করেছি তা লক্ষ্য করি:
"নেট .৪.৫-এ নতুন অ্যাসিনক্রোনাস পদ্ধতির জন্য, তাদের আচরণ হ'ল সিঙ্ক্রোনাস পদ্ধতিগুলির সাথে হ'ল এক উল্লেখযোগ্য ব্যতিক্রম ব্যতীত: নন-সিক্যুয়াল মোডে রিডএেন্সিক।"
কি ?!!!
তাই আমি নিয়মিত / অ্যাসিঙ্ক কল এবং CommandBehavior.SequentialAccess
/ এর সাথে অ্যাডো.নেট অন্তর্ভুক্ত করার জন্য আমার মানদণ্ডগুলি প্রসারিত করি CommandBehavior.Default
এবং এখানে একটি বিস্ময়! :
অ্যাডো.নেটের সাথে আমাদের ঠিক একই আচরণ রয়েছে !!! Facepalm ...
আমার চূড়ান্ত উপসংহারটি হল : EF 6 বাস্তবায়নে একটি বাগ আছে। এটা তোলে টগল উচিত CommandBehavior
করার SequentialAccess
একটি ASYNC কলের জন্য একটি সমন্বিত একটি টেবিলের উপর তৈরি করা হয় যখন binary(max)
কলাম। প্রক্রিয়াটি ধীরগতিতে অনেক বেশি টাস্ক তৈরির সমস্যাটি অ্যাডো.নেট দিকে রয়েছে। EF সমস্যাটি হ'ল এটি অ্যাডো.নেটকে যেমন ব্যবহার করা উচিত তেমন ব্যবহার করে না।
এখন আপনি জানেন যে EF6 অ্যাসিঙ্ক পদ্ধতিগুলি ব্যবহার করার পরিবর্তে আপনাকে নিয়মিত অ-অ্যাসিঙ্ক পদ্ধতিতে EF কল করতে হবে, এবং তারপরে TaskCompletionSource<T>
ফলাফলটি অ্যাসিঙ্ক পদ্ধতিতে ফেরত দিতে একটি ব্যবহার করতে হবে।
নোট 1: লজ্জাজনক ত্রুটির কারণে আমি আমার পোস্টটি সম্পাদনা করেছি .... আমি স্থানীয়ভাবে নয়, নেটওয়ার্কের মাধ্যমে আমার প্রথম পরীক্ষাটি করেছি এবং সীমিত ব্যান্ডউইথ ফলাফলগুলি বিকৃত করেছে। এখানে আপডেট ফলাফল।
দ্রষ্টব্য 2: আমি অন্য পরীক্ষার ক্ষেত্রে আমার পরীক্ষাটি প্রসারিত করি নি (উদাহরণস্বরূপ: nvarchar(max)
প্রচুর ডেটা সহ) তবে একই আচরণ হওয়ার সম্ভাবনা রয়েছে।
দ্রষ্টব্য 3: ToList()
মামলার জন্য সাধারণ কিছু , 12% সিপিইউ (আমার সিপিইউ = 1 লজিক্যাল কোরের 1/8)। কিছু অস্বাভাবিক কিছু হ'ল ToListAsync()
মামলার সর্বোচ্চ 20% , কারণ শিডিয়ুলার সমস্ত ট্র্যাড ব্যবহার করতে না পারলে। এটি সম্ভবত অনেকগুলি টাস্ক তৈরি হওয়ার কারণে, বা টিডিএস পার্সারে কোনও বাধা থাকতে পারে, আমি জানি না ...