Spanned เป็นห้องสมุด. NET ที่มีประสิทธิภาพสูงและเป็นศูนย์ที่แนะนำทางเลือกที่เข้ากันได้กับ BCL ที่เป็นที่นิยมและจัดหาโซลูชั่นเวกเตอร์สำหรับการดำเนินงานทั่วไปในช่วง
ในการเริ่มต้นใช้งานก่อนอื่นเพิ่มแพ็คเกจที่สาดลงในโครงการของคุณ คุณสามารถทำได้โดยเรียกใช้คำสั่งต่อไปนี้:
dotnet add package Spannedหรือคุณสามารถติดตั้งผ่านคอนโซลแพ็คเกจ Manager ด้วยคำสั่งนี้:
Install-Package Spannedโปรดทราบว่า. NET ได้สะสมเส้นทางการเพิ่มประสิทธิภาพจำนวนมากเมื่อเวลาผ่านไปทั้งเฟรมเวิร์กและขึ้นอยู่กับรันไทม์ ดังนั้นมันจึงกลายเป็นความท้าทายอย่างมากกับเพื่อนบ้านที่ได้รับการปรับปรุงให้ดีที่สุดสำหรับเฟรมเวิร์กที่แตกต่างกันในรหัสฐานเดียว ดังนั้นฉันจึงตัดสินใจได้ยากที่จะสนับสนุนเฟรมเวิร์กเพียงหนึ่งเฟรมต่อเวอร์ชันของห้องสมุดนี้ คุณสามารถค้นหารายการเวอร์ชันไลบรารีและเวอร์ชันเฟรมเวิร์กที่รองรับได้ในตารางด้านล่าง:
| .NET มาตรฐาน 2.0 | .NET มาตรฐาน 2.1 | .net 8+ | |
|---|---|---|---|
ในขณะที่มันอาจจะดึงดูดให้ใช้ v0.0.1 สำหรับทุกความต้องการของคุณเนื่องจากได้รับการสนับสนุนสำหรับ. NET Standard 2.0 และการยอมรับอย่างกว้างขวางของ. NET Standard 2.0 ทุกวันนี้ฉันขอแนะนำให้ทำเช่นนั้น แทบจะไม่มีการปรับให้เหมาะสมอย่างใดอย่างหนึ่งที่สามารถทำได้โดยใช้กรอบมรดกนี้ แม้แต่ Span ที่เรารักของเราสามารถเข้าถึงได้ใน. NET Standard 2.0 ผ่านแพ็คเกจ System.Memory NuGet เป็นที่รู้จักกันในชื่อ "Slow Span" เพราะ Span นั้นไม่มีอะไรมากไปกว่า ArraySegment ที่คิดค้นขึ้นมาใหม่ขาดการสนับสนุนที่เหมาะสมในด้านรันไทม์/JIT ดังนั้นโปรดเลือกเวอร์ชันแพ็คเกจที่ดีที่สุดสำหรับสภาพแวดล้อมของคุณไม่ใช่รุ่นที่ดูเหมือนจะพอดีกับพวกเขาทั้งหมด
ก่อนที่เราจะได้รับรายละเอียดเรามาพูดคุยเกี่ยวกับกรณีขอบทั่วไปที่คุณอาจพบในขณะที่ใช้ห้องสมุดนี้และตอบคำถาม: "ทำไม X ไม่ได้เป็นส่วนหนึ่งของ. NET?" ในระยะสั้นทุกสิ่งที่คุณสามารถหาได้ที่นี่ใช้งานง่าย และ ใช้งานง่ายในทางที่ผิด
เริ่มต้นด้วยจุดที่ชัดเจน ห้องสมุดนี้ได้รับการออกแบบมาโดยเฉพาะสำหรับสถานการณ์ที่คุณต่อสู้กับฟันและเล็บสำหรับไบต์ที่จัดสรรทุกครั้งและทุก ๆ นาโนวินาทีของเวลาดำเนินการในเส้นทางที่สำคัญอย่างยิ่ง มันไม่ได้หมายถึงการแก้ปัญหาขนาดเดียวที่เหมาะกับ Codebase ทั้งหมดของคุณ การผสมผสานประเภทจากไลบรารีนี้อย่างไร้เหตุผลในทุกสถานการณ์ที่เป็นไปได้อาจลดประสิทธิภาพโดยรวมของแอปพลิเคชันของคุณแทนที่จะปรับปรุง
โปรดจำไว้ว่าอย่าดำดิ่งก่อนเข้าสู่มหาสมุทรนาโนที่เหมาะสมจนกระทั่งคุณแน่ใจว่าจำเป็น
ข้อผิดพลาดทั่วไปที่ต้องหลีกเลี่ยงคือการผ่านประเภทใด ๆ ที่จัดทำโดยห้องสมุดนี้โดยค่า (ใช่สิ่งนี้เพียงอย่างเดียวควรช่วยให้คุณเข้าใจว่าทำไมสิ่งนี้ไม่ควรและจะไม่เป็นส่วนหนึ่งของ BCL) ตัวอย่างเช่นในขณะที่รหัสต่อไปนี้อาจปรากฏได้ดีจริง ๆ แล้วมันเป็นหายนะ:
ValueStringBuilder sb = new ValueStringBuilder ( 16 ) ;
sb . AppendUserName ( user ) ;
// ...
public static void AppendUserName ( this ValueStringBuilder sb , User user )
{
sb . Append ( user . FirstName ) ;
sb . Append ( ' ' ) ;
sb . Append ( user . LastName ) ;
} การต่อท้ายกับตัวสร้างสตริงอาจขยายบัฟเฟอร์ภายใน อย่างไรก็ตามเนื่องจากเราผ่าน ValueStringBuilder ของเราโดยค่า (เช่นคัดลอกมัน) ValueStringBuilder ดั้งเดิมจะไม่ตระหนักถึงมันและจะใช้บัฟเฟอร์ที่กำจัดไปแล้วต่อไป
ในขณะที่วิธีการนี้อาจดูเหมือนจะทำงานร่วมกับอินพุตที่ถูกฆ่าเชื้อในระหว่างการทดสอบ แต่บางครั้งก็ล้มเหลวโดยไม่เพียง แต่การทำลายรหัสของคุณ แต่ยังรวมถึงบางส่วนของแอปของคุณโดยใช้บัฟเฟอร์ที่สำเนาของ ValueStringBuilder ได้กลับไปที่สระว่ายน้ำแล้วจึงสามารถนำกลับมาใช้ใหม่ได้
คุณอาจพยายามฉลาดเกี่ยวกับเรื่องนี้และแก้ไขปัญหาโดยการเขียนวิธีการขยายที่มีปัญหาอีกครั้งดังนี้:
public static void AppendUserName ( this in ValueStringBuilder sb , User user )
{
sb . Append ( user . FirstName ) ;
sb . Append ( ' ' ) ;
sb . Append ( user . LastName ) ;
} ตอนนี้ ValueStringBuilder ถูกส่งผ่านโดยการอ้างอิงดังนั้นจึงไม่ควรมีปัญหาใช่ไหม? ไม่ in ดัดแปลงมีอยู่เพื่อลดค่าใช้จ่ายในการคัดลอกอินสแตนซ์ประเภทมูลค่าทั้งหมดโดยส่งผ่านการอ้างอิงไปยังวิธีการในขณะที่รักษาความหมายราวกับว่ามันถูกส่งผ่านตามมูลค่า ซึ่งหมายความว่าการปรับเปลี่ยนสถานะใด ๆ ของ ValueStringBuilder ที่ให้ไว้จะไม่ถูกเผยแพร่ไปยังอินสแตนซ์ดั้งเดิม ดังนั้นเรายังคงมีปัญหาเดียวกันในมือของเรา วิธีที่ถูกต้องเพียงอย่างเดียวในการใช้วิธีการที่อาจแก้ไขสถานะภายในของอินสแตนซ์ประเภทค่าคือการส่งผ่านจริงโดยอ้างอิง:
ValueStringBuilder sb = new ValueStringBuilder ( 16 ) ;
AppendUserName ( ref sb , user ) ;
// ...
public static void AppendUserName ( ref ValueStringBuilder sb , User user )
{
sb . Append ( user . FirstName ) ;
sb . Append ( ' ' ) ;
sb . Append ( user . LastName ) ;
}ในขณะที่ไม่แฟนซีอย่างที่บางคนต้องการให้เป็นโซลูชันนี้มีประโยชน์ในการทำงานจริง
ประเภทส่วนใหญ่ที่จัดทำโดยไลบรารีนี้กำหนดวิธี Dispose() ช่วยให้การใช้งานกับคำหลักที่ using ตามที่สามารถเห็นได้ด้านล่าง:
using ValueStringBuilder sb = new ValueStringBuilder ( stackalloc char [ 16 ] ) ;
Foo ( ref sb ) ;
return sb . ToString ( ) ; อย่างไรก็ตามนี่ไม่ได้หมายความว่า ควร ใช้กับคำหลักที่ using เป็นเรื่องสำคัญที่ต้องจำไว้ว่ารหัสด้านบนลดลงจริง ๆ :
ValueStringBuilder sb = new ValueStringBuilder ( stackalloc char [ 16 ] ) ;
try
{
Foo ( ref sb ) ;
return sb . ToString ( ) ;
}
finally
{
sb . Dispose ( ) ;
} การสร้างและการจัดการภูมิภาคที่ได้รับการป้องกันนั้นไม่ฟรี เมื่อพิจารณาถึงการมุ่งเน้นไปที่การเพิ่มประสิทธิภาพของนาโนผลกระทบที่นี่เป็นที่สังเกตได้ ดังนั้นจึงควรเรียกใช้ Dispose() ด้วยตนเอง ():
ValueStringBuilder sb = new ValueStringBuilder ( stackalloc char [ 16 ] ) ;
Foo ( ref sb ) ;
string result = sb . ToString ( ) ;
sb . Dispose ( ) ;
return result ;หรือตรวจสอบว่าวิธีสุดท้ายที่คุณเรียกในประเภทที่กำหนดมีการโอเวอร์โหลดที่ทำงานโดยอัตโนมัติหรือไม่:
ValueStringBuilder sb = new ValueStringBuilder ( stackalloc char [ 16 ] ) ;
Foo ( ref sb ) ;
return sb . ToString ( dispose : true ) ;ใน Modern .NET เป็นเรื่องปกติที่จะพบรูปแบบต่อไปนี้:
หรือแสดงแนวคิดเดียวกันในรหัส:
const int StackAllocByteLimit = 1024 ;
T [ ] ? spanSource ;
scoped Span < T > span ;
if ( sizeof ( T ) * length > StackAllocByteLimit )
{
spanSource = ArrayPool < T > . Shared . Rent ( length ) ;
span = spanSource . AsSpan ( 0 , length ) ;
}
else
{
spanSource = null ;
span = stackalloc T [ length ] ;
}
DoSomeWorkWithSpan ( span ) ;
if ( spanSource is not null )
{
ArrayPool < T > . Shared . Return ( spanSource ) ;
} ไม่ใช่แผ่นหม้อไอน้ำที่สวยที่สุดใช่ไหม ตรรกะที่แท้จริงมักจะถูกฝังอยู่ด้วยซึ่งอยู่ไกลจากอุดมคติ นี่คือปัญหาที่แน่นอน SpanOwner มีวัตถุประสงค์เพื่อแก้ไข นี่คือตรรกะเดียวกัน แต่แผ่นหม้อไอน้ำทั้งหมดถูกซ่อนอยู่หลัง SpanOwner :
SpanOwner < T > owner = SpanOwner < T > . ShouldRent ( length ) ? SpanOwner < T > . Rent ( length ) : stackalloc T [ length ] ;
Span < T > span = owner . Span ;
DoSomeWorkWithSpan ( span ) ;
owner . Dispose ( ) ; เขียนง่ายกว่ามากในการอ่านและที่สำคัญที่สุดวิธีนี้ให้ประสิทธิภาพที่เหมือนกันเพราะ SpanOwner ได้รับการออกแบบให้ไม่สามารถใช้งานได้อย่างสมบูรณ์ สามารถกำจัดได้อย่างสมบูรณ์จากรหัสของคุณโดย JIT:
| วิธี | หมายถึง | stddev | อัตราส่วน | ขนาดรหัส |
|---|---|---|---|---|
| ไม่มี _spanowner_int32 | 5.134 ns | 0.0425 ns | 1.00 | 315 b |
| with_spanowner_int32 | 4.908 ns | 0.0168 ns | 0.96 | 310 b |
ValueStringBuilder เป็นการดำเนินการอีกครั้งของ StringBuilder ที่ออกแบบมาเพื่อรองรับบัฟเฟอร์ที่จัดสรรสแต็ก มีความสามารถในการใช้พูลอาร์เรย์ที่ใช้ร่วมกันเพื่อขยายบัฟเฟอร์ภายในเมื่อจำเป็น ValueStringBuilder เหมาะสำหรับการสร้างสตริงขนาดกะทัดรัดที่สามารถพอดีกับสแต็ก อย่างไรก็ตามไม่ควรใช้สำหรับสิ่งอื่นใดเพราะการดำเนินการในสตริงที่มีขนาดใหญ่ขึ้นอาจและจะลดประสิทธิภาพโดยรวมของแอพของคุณ ความสามารถที่แท้จริงของ ValueStringBuilder เกิดขึ้นเมื่อคุณต้องการสร้างลำดับอักขระสั้น ๆ ที่ไม่จำเป็นต้องเป็นรูปเป็นสตริงเลย
ValueStringBuilder สะท้อนคุณสมบัติทั้งหมดของ StringBuilder และประสบความสำเร็จในการทดสอบชุดชุดเดียวกันทำให้สามารถทำหน้าที่แทนที่ได้อย่างราบรื่นในสถานการณ์ส่วนใหญ่
// Note that providing a capacity instead of a buffer will force
// the builder to rent an array from `ArrayPool<char>.Shared`.
ValueStringBuilder sb = new ( stackalloc char [ 256 ] ) ;
// `ValueStringBuilder` provides a custom interpolated string handler,
// ensuring such operations do not allocate any new strings.
sb . Append ( $ "Hello, { user . Name } ! Your ID is: { user . id } " ) ;
// Unlike `StringBuilder`, `ValueStringBuilder` can be represented
// as a readonly span. Thus, you don't need to actually materialize
// the string you've built in lots of cases.
DisplayWelcome ( ( ReadOnlySpan < char > ) sb ) ;
// Remember to dispose of the builder to return
// a rented buffer, if any, back to the pool.
sb . Dispose ( ) ; ValueList<T> เป็นการดำเนินการอีกครั้งของ List<T> ออกแบบมาเพื่อรองรับบัฟเฟอร์ที่จัดสรรสแต็ก มีความสามารถในการใช้พูลอาร์เรย์ที่ใช้ร่วมกันเพื่อขยายบัฟเฟอร์ภายในเมื่อจำเป็น ValueList<T> เหมาะสำหรับการประมวลผลข้อมูลจำนวนเล็กน้อยที่สามารถพอดีกับสแต็ก อย่างไรก็ตามไม่ควรใช้สำหรับสิ่งอื่นใดเนื่องจากการดำเนินการในชุดข้อมูลขนาดใหญ่อาจและจะลดประสิทธิภาพโดยรวมของแอพของคุณ
ValueList<T> สะท้อนคุณสมบัติทั้งหมดของ List<T> และประสบความสำเร็จในการทดสอบชุดชุดเดียวกันทำให้สามารถทำหน้าที่แทนที่ได้อย่างราบรื่นในสถานการณ์ส่วนใหญ่
// Note that providing a capacity instead of a buffer will force
// the list to rent an array from `ArrayPool<T>.Shared`.
ValueList < int > list = new ( stackalloc int [ 10 ] ) ;
list . Add ( 0 ) ;
list . Add ( 1 ) ;
list . Add ( 2 ) ;
DoSomethingWithIntegers ( ( ReadOnlySpan < int > ) list ) ;
// Remember to dispose of the list to return
// a rented buffer, if any, back to the pool.
list . Dispose ( ) ; ValueStack<T> เป็นการดำเนินการใหม่ของ Stack<T> ออกแบบมาเพื่อรองรับบัฟเฟอร์ที่จัดสรรสแต็ก มีความสามารถในการใช้พูลอาร์เรย์ที่ใช้ร่วมกันเพื่อขยายบัฟเฟอร์ภายในเมื่อจำเป็น ValueStack<T> เหมาะสำหรับการประมวลผลข้อมูลจำนวนเล็กน้อยที่สามารถพอดีกับสแต็ก อย่างไรก็ตามไม่ควรใช้สำหรับสิ่งอื่นใดเนื่องจากการดำเนินการในชุดข้อมูลขนาดใหญ่อาจและจะลดประสิทธิภาพโดยรวมของแอพของคุณ
ValueStack<T> สะท้อนคุณสมบัติทั้งหมดของ Stack<T> และประสบความสำเร็จในการทดสอบชุดชุดเดียวกันทำให้สามารถทำหน้าที่แทนที่ได้อย่างราบรื่นในสถานการณ์ส่วนใหญ่
// Note that providing a capacity instead of a buffer will force
// the stack to rent an array from `ArrayPool<T>.Shared`.
ValueStack < int > stack = new ( stackalloc int [ 10 ] ) ;
stack . Push ( 0 ) ;
stack . Push ( 1 ) ;
stack . Push ( 2 ) ;
DoSomethingWithIntegers ( ( ReadOnlySpan < int > ) stack ) ;
// Remember to dispose of the stack to return
// a rented buffer, if any, back to the pool.
stack . Dispose ( ) ; ValueQueue<T> เป็นการดำเนินการอีกครั้งของ Queue<T> ที่ออกแบบมาเพื่อรองรับบัฟเฟอร์ที่จัดสรรสแต็ก มีความสามารถในการใช้พูลอาร์เรย์ที่ใช้ร่วมกันเพื่อขยายบัฟเฟอร์ภายในเมื่อจำเป็น ValueQueue<T> เหมาะสำหรับการประมวลผลข้อมูลจำนวนเล็กน้อยที่สามารถพอดีกับสแต็ก อย่างไรก็ตามไม่ควรใช้สำหรับสิ่งอื่นใดเนื่องจากการดำเนินการในชุดข้อมูลขนาดใหญ่อาจและจะลดประสิทธิภาพโดยรวมของแอพของคุณ
ValueQueue<T> สะท้อนคุณสมบัติทั้งหมดของ Queue<T> และผ่านการทดสอบชุดชุดเดียวกันได้สำเร็จทำให้สามารถใช้งานได้อย่างราบรื่นในสถานการณ์ส่วนใหญ่
// Note that providing a capacity instead of a buffer will force
// the queue to rent an array from `ArrayPool<T>.Shared`.
ValueQueue < int > queue = new ( stackalloc int [ 10 ] ) ;
queue . Enqueue ( 0 ) ;
queue . Enqueue ( 1 ) ;
queue . Enqueue ( 2 ) ;
DoSomethingWithIntegers ( ( ReadOnlySpan < int > ) queue ) ;
// Remember to dispose of the queue to return
// a rented buffer, if any, back to the pool.
queue . Dispose ( ) ; ValueSet<T> เป็นการดำเนินการใหม่ของ HashSet<T> ที่ออกแบบมาเพื่อรองรับบัฟเฟอร์ที่จัดสรรสแต็ก มีความสามารถในการใช้พูลอาร์เรย์ที่ใช้ร่วมกันเพื่อขยายบัฟเฟอร์ภายในเมื่อจำเป็น ValueSet<T> เหมาะสำหรับการประมวลผลข้อมูลจำนวนเล็กน้อยที่สามารถพอดีกับสแต็ก อย่างไรก็ตามไม่ควรใช้สำหรับสิ่งอื่นใดเนื่องจากการดำเนินการในชุดข้อมูลขนาดใหญ่อาจและจะลดประสิทธิภาพโดยรวมของแอพของคุณ
ValueSet<T> สะท้อนคุณสมบัติทั้งหมดของ HashSet<T> และผ่านการทดสอบชุดชุดเดียวกันได้สำเร็จทำให้สามารถใช้งานได้อย่างราบรื่นในสถานการณ์ส่วนใหญ่
// Note that providing a capacity instead of a buffer will force
// the set to rent an array from `ArrayPool<T>.Shared`.
ValueSet < int > set = new ( stackalloc int [ 10 ] ) ;
set . Add ( 0 ) ;
set . Add ( 1 ) ;
set . Add ( 2 ) ;
DoSomethingWithIntegers ( ( ReadOnlySpan < int > ) set ) ;
// Remember to dispose of the set to return
// a rented buffer, if any, back to the pool.
set . Dispose ( ) ; ValueDictionary<TKey, TValue> เป็นการดำเนินการใหม่ของ Dictionary<TKey, TValue> ออกแบบมาเพื่อรองรับบัฟเฟอร์ที่จัดสรรสแต็ก มีความสามารถในการใช้พูลอาร์เรย์ที่ใช้ร่วมกันเพื่อขยายบัฟเฟอร์ภายในเมื่อจำเป็น ValueDictionary<TKey, TValue> เหมาะสำหรับการประมวลผลข้อมูลจำนวนเล็กน้อยที่สามารถพอดีกับสแต็ก อย่างไรก็ตามไม่ควรใช้สำหรับสิ่งอื่นใดเนื่องจากการดำเนินการในชุดข้อมูลขนาดใหญ่อาจและจะลดประสิทธิภาพโดยรวมของแอพของคุณ
ValueDictionary<TKey, TValue> สะท้อนคุณสมบัติทั้งหมดของ Dictionary<TKey, TValue> และประสบความสำเร็จในการทดสอบชุดชุดเดียวกันทำให้สามารถใช้งานได้อย่างราบรื่นในสถานการณ์ส่วนใหญ่
// Note that providing a capacity instead of a buffer will force
// the dictionary to rent an array from `ArrayPool<T>.Shared`.
ValueDictionary < int , string > dictionary = new ( 10 ) ;
dictionary . Add ( 0 , "zero" ) ;
dictionary . Add ( 1 , "one" ) ;
dictionary . Add ( 2 , "two" ) ;
DoSomethingWithPairs ( ( ReadOnlySpan < KeyValuePair < int , string > > ) dictionary ) ;
// Remember to dispose of the dictionary to return
// a rented buffer, if any, back to the pool.
dictionary . Dispose ( ) ; .Min() และ .Max() เป็นวิธีการขยายที่สามารถช่วยคุณค้นหาค่าต่ำสุด/สูงสุดในช่วง พวกเขาจะได้รับเวกเตอร์สำหรับทุกประเภทที่รองรับซึ่งแตกต่างจาก Enumerable.Min() และ Enumerable.Max() ซึ่งไม่ได้ให้การปรับให้เหมาะสมสำหรับหมายเลขจุดลอยตัว
อย่างไรก็ตามมีปัญหาเล็กน้อยเกี่ยวกับหมายเลขจุดลอยตัว (เช่น float และ double ) และชื่อของปัญหานี้คือ NaN อย่างที่คุณอาจรู้ว่า NaN ไม่ได้มากกว่าและน้อยกว่าจำนวนใด ๆ และไม่เท่ากับตัวเลขใด ๆ แม้แต่ตัวเอง ดังนั้นหาก NaN มีอยู่ในลำดับที่ให้ไว้มันสามารถขัดขวางการใช้งานที่ไร้เดียงสาที่ต้องอาศัยผลการดำเนินการเปรียบเทียบเป็นประจำเท่านั้น ดังนั้นหาก NaN มีอยู่ในลำดับที่ให้ไว้มันสามารถขัดขวางการใช้งานที่ไร้เดียงสาที่ต้องอาศัยผลการดำเนินการเปรียบเทียบเป็นประจำเท่านั้น ดังนั้นการไม่บัญชีสำหรับการเปรียบเทียบจุดลอยตัวนี้ไม่ใช่ตัวเลือก
Spanned การจัดการเพื่อใช้การตรวจสอบที่เกี่ยวข้องกับ NaN ทั้งหมดในลักษณะที่มีประสิทธิภาพสูงให้ประสิทธิภาพที่เพิ่มขึ้นอย่างมีนัยสำคัญเหนือโซลูชันที่ไม่ได้รับการปรับแต่ง อย่างไรก็ตามประสิทธิภาพอาจจะดียิ่งขึ้นถ้าเราไม่จำเป็นต้องบัญชีสำหรับ NaN นี่คือเหตุผลที่ .UnsafeMin() และ .UnsafeMax() มีอยู่ วิธีการเหล่านี้มีความเฉพาะเจาะจงสำหรับช่วงเวลาที่มีตัวเลขลอยตัวและดำเนินการเปรียบเทียบโดยไม่ยอมรับการมีอยู่ของ NaN กำจัดการตรวจสอบที่เกี่ยวข้องทั้งหมด ดังนั้นหากคุณมั่นใจว่าช่วงเวลาของจุดลอยตัวจะถูกสุขลักษณะและไม่สามารถมี NaN S ใด ๆ ได้คุณสามารถบีบประสิทธิภาพได้มากขึ้นจากการดำเนินงาน .Min() และ .Max()
ในขณะที่ความแตกต่างระหว่าง .Min() และ .UnsafeMin() อาจไม่ชัดเจนมาก:
| วิธี | หมายถึง | stddev | อัตราส่วน | ขนาดรหัส |
|---|---|---|---|---|
| min_loop_single | 3,919.5 ns | 15.75 ns | 1.00 | 207 B |
| min_linq_single | 4,030.3 ns | 37.38 ns | 1.03 | 570 b |
| min_span_single | 611.1 ns | 8.55 ns | 0.16 | 534 b |
| unsafemin_span_single | 569.0 ns | 1.82 ns | 0.15 | 319 b |
ช่องว่างประสิทธิภาพมีความสำคัญระหว่าง .Max() และ .UnsafeMax() :
| วิธี | หมายถึง | stddev | อัตราส่วน | ขนาดรหัส |
|---|---|---|---|---|
| max_loop_single | 3,849.2 ns | 36.97 NS | 1.00 | 215 b |
| max_linq_single | 3,936.4 ns | 53.51 ns | 1.02 | 643 b |
| max_span_single | 901.7 NS | 7.12 ns | 0.23 | 606 B |
| unsafemax_span_single | 551.8 ns | 3.06 ns | 0.14 | 321 b |
.Sum() เป็นวิธีการขยายที่สามารถช่วยคุณคำนวณผลรวมของค่าทั้งหมดในช่วง มันเป็นเวกเตอร์สำหรับทุกประเภทที่รองรับซึ่งแตกต่างจาก Enumerable.Sum() ซึ่งไม่เพียง แต่ขาด vectorization แต่ไม่ได้ให้โอเวอร์โหลดสำหรับประเภทตัวเลขส่วนใหญ่ออกจากกล่องเลย
คล้ายกับ .Min() และ .Max() , .Sum() มีแฝดชั่วร้ายที่ใช้ชื่อ .UnsafeSum() วิธีการพื้นฐานจะทำให้เกิด OverflowException หากการคำนวณผลรวมส่งผลให้เป็นจำนวนเต็มล้น/ต่ำ แน่นอนว่าผู้คุมล้นมามีค่าใช้จ่ายและมันก็ไม่ใช่สิ่งที่ไม่สำคัญ ดังนั้นหากอินพุตของคุณถูกสุขลักษณะและไม่สามารถทำให้เกิดการล้นหรือหากจำนวนเต็มล้นเป็นพฤติกรรมที่คาดหวังในบริบทการทำงานของคุณอย่าลังเลที่จะใช้ .UnsafeSum() มันเร็วกว่า .Sum() สองเท่าเร็วกว่าการคำนวณผลรวมภายในลูปและเร็วกว่าการคำนวณผลรวมผ่าน LINQ: 34 เท่า
| วิธี | หมายถึง | stddev | อัตราส่วน | ขนาดรหัส |
|---|---|---|---|---|
| sum_loop_int16 | 3,820.0 ns | 7.04 ns | 1.00 | 128 b |
| sum_linq_int16 | 14,472.6 ns | 281.83 NS | 3.80 | 732 b |
| sum_span_int16 | 214.6 NS | 2.43 ns | 0.06 | 413 B |
| unsafesum_span_int16 | 111.8 ns | 1.00 ns | 0.03 | 200 B |
.LongSum() เป็นวิธีการขยายที่สามารถช่วยคุณคำนวณผลรวมของค่าทั้งหมดในช่วงโดยใช้ตัวสะสม 64 บิต (เช่น long สำหรับจำนวนเต็มที่ลงนาม, ulong double จำนวน float int.MaxValue + int.MaxValue ได้ลงชื่อ พิมพ์ int ) มันเป็นเวกเตอร์สำหรับทุกประเภทที่รองรับและไม่มีทางเลือกที่เหมาะสมใน LINQ (ดังนั้นเกณฑ์มาตรฐานด้านล่างนั้นไม่ยุติธรรมเล็กน้อย)
.LongSum() ไม่มีคู่ "ไม่ปลอดภัย" เพราะแม้แต่ช่วงที่ (long)int.MaxValue * (long)int.MaxValue < long.MaxValue ที่สุดเท่าที่จะเป็น int.MaxValue int
| วิธี | หมายถึง | stddev | อัตราส่วน | ขนาดรหัส |
|---|---|---|---|---|
| longsum_loop_int16 | 2,537.1 ns | 21.30 ns | 1.00 | 98 b |
| longsum_linq_int16 | 14,372.0 ns | 130.00 น. | 5.67 | 734 b |
| longsum_span_int16 | 251.0 ns | 2.38 ns | 0.10 | 390 b |
.Average() เป็นวิธีการขยายที่สามารถช่วยคุณคำนวณค่าเฉลี่ยของค่าทั้งหมดในช่วง มันเป็นเวกเตอร์สำหรับทุกประเภทที่รองรับซึ่งแตกต่างจาก Enumerable.Average() ซึ่งให้การปรับให้เหมาะสมสำหรับจำนวนเต็ม 32 บิต (เช่น int s)
ภายใต้ประทุน, .Average() ใช้ .LongSum() เพื่อคำนวณผลรวมขององค์ประกอบทั้งหมดในขณะที่หลีกเลี่ยงการล้นจำนวนเต็ม อย่างไรก็ตามหากอินพุตของคุณถูกฆ่าเชื้อและไม่สามารถทำให้เกิดขึ้นได้คุณสามารถเปลี่ยนเป็น .UnsafeAverage() ซึ่งใช้ .UnsafeSum() และไม่ได้ใช้เวลาดำเนินการอันมีค่ากับยามล้น
| วิธี | หมายถึง | stddev | อัตราส่วน | ขนาดรหัส |
|---|---|---|---|---|
| ค่าเฉลี่ย _loop_int16 | 2,482.1 ns | 20.04 ns | 1.00 | 241 B |
| ค่าเฉลี่ย _linq_int16 | 13,198.2 ns | 97.67 NS | 5.31 | 1,016 b |
| ค่าเฉลี่ย _span_int16 | 257.8 ns | 3.61 ns | 0.10 | 593 b |
| unsafeaverage_span_int16 | 116.7 ns | 1.27 ns | 0.05 | 128 b |
.FillSequential() เป็นวิธีการขยายที่สามารถช่วยให้คุณเติมช่วงที่กำหนดด้วยค่าตัวเลขตามลำดับ มันเป็นเวกเตอร์สำหรับทุกประเภทที่รองรับและไม่มีทางเลือกใน LINQ
| วิธี | หมายถึง | stddev | อัตราส่วน | ขนาดรหัส |
|---|---|---|---|---|
| FillSeperential_loop_int16 | 2,499.4 ns | 28.47 NS | 1.00 | 118 b |
| FillSeperential_SPAN_INT16 | 169.2 ns | 0.18 ns | 0.07 | 660 b |
.IndexOf() , .LastIndexOf() และ .Contains() อาจดูคุ้นเคยกับคุณเพราะวิธีการเหล่านี้จัดทำโดย MemoryExtensions อย่างไรก็ตามมีสองปัญหากับพวกเขา:
IEquatable<T> ทำให้ไม่สามารถเข้าถึงได้ในบริบททั่วไปที่ไม่ได้ผูกไว้IEqualityComparer<T>การใช้งานวิธีการเหล่านี้ที่จัดทำโดยที่อยู่ห้องสมุดนี้ทั้งสองประเด็นนี้
ได้รับใบอนุญาตภายใต้เงื่อนไขของใบอนุญาต MIT