การใช้ LINQ กับคอลเล็กชั่นอะไรคือความแตกต่างระหว่างบรรทัดโค้ดต่อไปนี้?
if(!coll.Any(i => i.Value))
และ
if(!coll.Exists(i => i.Value))
อัปเดต 1
เมื่อฉันถอดแยกชิ้นส่วน.Existsดูเหมือนว่าไม่มีรหัส
อัปเดต 2
ใครรู้ว่าทำไมไม่มีรหัสสำหรับหนึ่งนี้
การใช้ LINQ กับคอลเล็กชั่นอะไรคือความแตกต่างระหว่างบรรทัดโค้ดต่อไปนี้?
if(!coll.Any(i => i.Value))
และ
if(!coll.Exists(i => i.Value))
อัปเดต 1
เมื่อฉันถอดแยกชิ้นส่วน.Existsดูเหมือนว่าไม่มีรหัส
อัปเดต 2
ใครรู้ว่าทำไมไม่มีรหัสสำหรับหนึ่งนี้
คำตอบ:
ดูเอกสารประกอบ
List.Exists (วิธีวัตถุ - MSDN)
พิจารณาว่า List (T) มีองค์ประกอบที่ตรงกับเงื่อนไขที่กำหนดโดยเพรดิเคตที่ระบุหรือไม่
สิ่งนี้มีอยู่ตั้งแต่. NET 2.0 ดังนั้นก่อน LINQ หมายถึงที่จะใช้กับผู้แทน Predicate แต่การแสดงออกแลมบ์ดานั้นเข้ากันได้แบบย้อนหลัง นอกจากนี้เพียงแค่รายการมีสิ่งนี้ (ไม่แม้แต่ IList)
IEnumerable.Any (วิธีการขยาย - MSDN)
กำหนดว่าองค์ประกอบใด ๆ ของลำดับเป็นไปตามเงื่อนไขหรือไม่
นี่เป็นสิ่งใหม่ใน. NET 3.5 และใช้ Func (TSource, bool) เป็นอาร์กิวเมนต์ดังนั้นนี่จึงมีวัตถุประสงค์เพื่อใช้กับแลมบ์ดานิพจน์และ LINQ
ในพฤติกรรมเหล่านี้เหมือนกัน
List<>วิธีเช่น
ความแตกต่างคือว่าใด ๆ เป็นวิธีการขยายสำหรับใด ๆ ที่IEnumerable<T>กำหนดไว้ใน System.Linq.Enumerable มันสามารถใช้กับIEnumerable<T>อินสแตนซ์ ใด ๆ
มีอยู่ไม่ปรากฏว่าเป็นวิธีการขยาย ฉันเดาว่า Coll List<T>เป็นประเภท ถ้ามีอยู่เป็นวิธีการอินสแตนซ์ซึ่งทำหน้าที่คล้ายกับใด ๆ
ในระยะสั้น , วิธีการที่เป็นหลักเดียวกัน หนึ่งทั่วไปมากกว่าอื่น ๆ
TLDR; ประสิทธิภาพที่ชาญฉลาดAnyดูเหมือนจะช้าลง (ถ้าฉันตั้งค่านี้อย่างถูกต้องเพื่อประเมินค่าทั้งสองในเวลาเดียวกัน)
var list1 = Generate(1000000);
var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
if (forceListEval != "sdsdf")
{
var s = string.Empty;
var start2 = DateTime.Now;
if (!list1.Exists(o => o == "0123456789012"))
{
var end2 = DateTime.Now;
s += " Exists: " + end2.Subtract(start2);
}
var start1 = DateTime.Now;
if (!list1.Any(o => o == "0123456789012"))
{
var end1 = DateTime.Now;
s +=" Any: " +end1.Subtract(start1);
}
if (!s.Contains("sdfsd"))
{
}
เครื่องกำเนิดรายการทดสอบ:
private List<string> Generate(int count)
{
var list = new List<string>();
for (int i = 0; i < count; i++)
{
list.Add( new string(
Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
.Select(s =>
{
var cryptoResult = new byte[4];
new RNGCryptoServiceProvider().GetBytes(cryptoResult);
return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
})
.ToArray()));
}
return list;
}
ด้วยบันทึก 10M
"ใด ๆ : 00: 00: 00.3770377 มีอยู่: 00: 00: 00.2490249"
ด้วยบันทึก 5M
"ใด ๆ : 00: 00: 00.0940094 มีอยู่: 00: 00: 00.1420142"
ด้วย 1M บันทึก
"ใด ๆ : 00: 00: 00.0180018 มีอยู่: 00: 00: 00.0090009"
ด้วย 500k (ฉันพลิกไปตามลำดับที่พวกเขาได้รับการประเมินเพื่อดูว่าไม่มีการดำเนินการเพิ่มเติมใด ๆ ที่เกี่ยวข้องกับสิ่งที่ทำงานก่อนหรือไม่)
"มีอยู่: 00: 00: 00.0050005 อะไรก็ได้: 00: 00: 00.0100010"
ด้วยการบันทึก 100k
"มีอยู่: 00: 00: 00.0010001 อะไรก็ได้: 00: 00: 00.0020002"
ดูเหมือนว่าAnyจะช้าลงด้วยขนาด 2
แก้ไข:สำหรับ 5 และ 10M บันทึกฉันเปลี่ยนวิธีที่มันสร้างรายการและExistsก็ช้ากว่าAnyที่แสดงถึงมีบางอย่างผิดปกติในวิธีที่ฉันกำลังทดสอบ
กลไกการทดสอบใหม่:
private static IEnumerable<string> Generate(int count)
{
var cripto = new RNGCryptoServiceProvider();
Func<string> getString = () => new string(
Enumerable.Repeat("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 13)
.Select(s =>
{
var cryptoResult = new byte[4];
cripto.GetBytes(cryptoResult);
return s[new Random(BitConverter.ToInt32(cryptoResult, 0)).Next(s.Length)];
})
.ToArray());
var list = new ConcurrentBag<string>();
var x = Parallel.For(0, count, o => list.Add(getString()));
return list;
}
private static void Test()
{
var list = Generate(10000000);
var list1 = list.ToList();
var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
if (forceListEval != "sdsdf")
{
var s = string.Empty;
var start1 = DateTime.Now;
if (!list1.Any(o => o == "0123456789012"))
{
var end1 = DateTime.Now;
s += " Any: " + end1.Subtract(start1);
}
var start2 = DateTime.Now;
if (!list1.Exists(o => o == "0123456789012"))
{
var end2 = DateTime.Now;
s += " Exists: " + end2.Subtract(start2);
}
if (!s.Contains("sdfsd"))
{
}
}
แก้ไข 2:ตกลงเพื่อกำจัดอิทธิพลใด ๆ จากการสร้างข้อมูลทดสอบฉันเขียนมันทั้งหมดไปยังไฟล์และตอนนี้อ่านจากที่นั่น
private static void Test()
{
var list1 = File.ReadAllLines("test.txt").Take(500000).ToList();
var forceListEval = list1.SingleOrDefault(o => o == "0123456789012");
if (forceListEval != "sdsdf")
{
var s = string.Empty;
var start1 = DateTime.Now;
if (!list1.Any(o => o == "0123456789012"))
{
var end1 = DateTime.Now;
s += " Any: " + end1.Subtract(start1);
}
var start2 = DateTime.Now;
if (!list1.Exists(o => o == "0123456789012"))
{
var end2 = DateTime.Now;
s += " Exists: " + end2.Subtract(start2);
}
if (!s.Contains("sdfsd"))
{
}
}
}
10M
"ใด ๆ : 00: 00: 00.1640164 มีอยู่: 00: 00: 00.0750075"
5M
"ใด ๆ : 00: 00: 00.0810081 มีอยู่: 00: 00: 00.0360036"
1M
"ใด ๆ : 00: 00: 00.0190019 มีอยู่: 00: 00: 00.0070007"
500k
"Any: 00: 00: 00.0120012 มีอยู่: 00: 00: 00.0040004"
เป็นความต่อเนื่องในคำตอบของ Matasเกี่ยวกับการเปรียบเทียบ
TL / DR : มีอยู่ () และใด ๆ () มีความรวดเร็วเท่ากัน
ก่อนปิด: การเปรียบเทียบโดยใช้นาฬิกาจับเวลาไม่แม่นยำ ( ดูคำตอบของ series0ne ในหัวข้อที่แตกต่างกัน แต่คล้ายกัน ) แต่มันแม่นยำกว่า DateTime มาก
วิธีรับการอ่านที่แม่นยำจริงๆคือการใช้การทำโปรไฟล์ประสิทธิภาพ แต่วิธีหนึ่งในการทำความเข้าใจว่าประสิทธิภาพของทั้งสองวิธีวัดกันอย่างไรโดยการดำเนินการโหลดทั้งสองวิธีแล้วเปรียบเทียบเวลาดำเนินการที่เร็วที่สุดของแต่ละวิธี ด้วยวิธีนี้มันไม่สำคัญว่า JITing และเสียงรบกวนอื่น ๆ จะให้การอ่านที่ไม่ดี (และเป็นเช่นนั้น ) เพราะการประหารทั้งสองเป็นการ " เข้าใจผิดอย่างเท่าเทียมกัน " อย่างสมเหตุสมผล
static void Main(string[] args)
{
Console.WriteLine("Generating list...");
List<string> list = GenerateTestList(1000000);
var s = string.Empty;
Stopwatch sw;
Stopwatch sw2;
List<long> existsTimes = new List<long>();
List<long> anyTimes = new List<long>();
Console.WriteLine("Executing...");
for (int j = 0; j < 1000; j++)
{
sw = Stopwatch.StartNew();
if (!list.Exists(o => o == "0123456789012"))
{
sw.Stop();
existsTimes.Add(sw.ElapsedTicks);
}
}
for (int j = 0; j < 1000; j++)
{
sw2 = Stopwatch.StartNew();
if (!list.Exists(o => o == "0123456789012"))
{
sw2.Stop();
anyTimes.Add(sw2.ElapsedTicks);
}
}
long existsFastest = existsTimes.Min();
long anyFastest = anyTimes.Min();
Console.WriteLine(string.Format("Fastest Exists() execution: {0} ticks\nFastest Any() execution: {1} ticks", existsFastest.ToString(), anyFastest.ToString()));
Console.WriteLine("Benchmark finished. Press any key.");
Console.ReadKey();
}
public static List<string> GenerateTestList(int count)
{
var list = new List<string>();
for (int i = 0; i < count; i++)
{
Random r = new Random();
int it = r.Next(0, 100);
list.Add(new string('s', it));
}
return list;
}
หลังจากรันโค้ดด้านบน 4 ครั้ง (ซึ่งจะทำ 1,000 Exists()และAny()ในรายการที่มีองค์ประกอบ 1,000,000) ก็ไม่ยากที่จะเห็นว่าวิธีการนั้นค่อนข้างเร็วเท่ากัน
Fastest Exists() execution: 57881 ticks
Fastest Any() execution: 58272 ticks
Fastest Exists() execution: 58133 ticks
Fastest Any() execution: 58063 ticks
Fastest Exists() execution: 58482 ticks
Fastest Any() execution: 58982 ticks
Fastest Exists() execution: 57121 ticks
Fastest Any() execution: 57317 ticks
มีคือความแตกต่างเล็กน้อย แต่มันคือความแตกต่างที่มีขนาดเล็กเกินไปที่จะไม่สามารถอธิบายได้ด้วยเสียงพื้นหลัง ฉันเดาจะเป็นไปได้ว่าถ้าใครจะทำ 10 000 หรือ 100 000 Exists()และAny()แทนที่แตกต่างกันเล็กน้อยจะหายไปมากหรือน้อย
นอกจากนี้จะใช้งานได้เฉพาะถ้า Value เป็นประเภทบูล โดยปกติจะใช้กับภาคแสดง เพรดิเคตใด ๆ โดยทั่วไปจะใช้พบว่ามีองค์ประกอบใด ๆ ที่เป็นไปตามเงื่อนไขที่กำหนด ที่นี่คุณเพิ่งทำแผนที่จากองค์ประกอบ i ของคุณไปยังคุณสมบัติบูล มันจะค้นหาคำว่า "i" ซึ่งมีค่าคุณสมบัติเป็นจริง เมื่อเสร็จแล้ววิธีการจะกลับมาจริง
เมื่อคุณแก้ไขการวัด - ตามที่กล่าวไว้ข้างต้น: สิ่งที่มีอยู่และการเพิ่มค่าเฉลี่ย - เราจะได้ผลลัพธ์ต่อไปนี้:
Executing search Exists() 1000 times ...
Average Exists(): 35566,023
Fastest Exists() execution: 32226
Executing search Any() 1000 times ...
Average Any(): 58852,435
Fastest Any() execution: 52269 ticks
Benchmark finished. Press any key.