ลิงค์วิทยาศาสตร์และเทคโนโลยี (7 สิงหาคม 2565)

ภาพบุคคล2018facebook.jpg

  1. ประสิทธิภาพการประมวลผลที่เพิ่มขึ้นอธิบายได้ถึง 94% ของการปรับปรุงประสิทธิภาพในด้านต่างๆ เช่น การทำนายสภาพอากาศ การพับของโปรตีน และการสำรวจน้ำมัน : เทคโนโลยีสารสนเทศเป็นตัวขับเคลื่อนของการปรับปรุงประสิทธิภาพในระยะยาวในสังคม หากเราหยุดปรับปรุงการประมวลผลของเรา ผลที่ตามมาอาจเลวร้าย
  2. ปะการังที่ปกคลุมแนวปะการัง Great Barrier Reef ได้ ถึงระดับสูงสุดนับตั้งแต่สถาบันวิทยาศาสตร์ทางทะเลแห่งออสเตรเลีย (AIMS) เริ่มเฝ้าติดตามเมื่อ 36 ปีก่อน
  3. ทฤษฎีชั้นนำของอัลไซเมอร์ สมมติฐานเกี่ยวกับอะไมลอยด์ ซึ่งกระตุ้นการวิจัยเป็นเวลาหลายปี ถูกสร้างขึ้นส่วนหนึ่งจากการฉ้อโกง ผู้เขียนการฉ้อโกงคือศาสตราจารย์ Sylvain Lesné ดูเหมือน ว่าทีมของเขาจะประกอบร่างโดยนำภาพถ่ายจากการทดลองต่างๆ มาปะติดปะต่อกัน อย่างมีประสิทธิภาพ พวกเขา “photoshopped” เอกสารทางวิทยาศาสตร์ของพวกเขา ไม่ใช่แค่ภาพเดียวหรือสองภาพ แต่อย่างน้อย 70 ภาพ โปรดทราบว่าการทดลองทางคลินิกทั้งหมดของยาที่พัฒนาขึ้น (ด้วยราคาสูง) บนสมมติฐานอะไมลอยด์ล้มเหลว ค่าใช้จ่ายทั้งหมดอยู่ในพันล้านดอลลาร์ ในขณะเดียวกัน ทฤษฎีและการรักษาที่แข่งขันกันถูกกีดกันโดยสมมติฐานอะไมลอยด์ที่น่าสนใจ น่าสนใจที่จะดูบทลงโทษแบบใด หากมี เลสเน่ได้รับจากการกระทำของเขา คุณอาจอ่านคำให้การของนักวิจัยที่ทำงานยากเพราะพวกเขาไม่เชื่อในสมมติฐานอะไมลอยด์ เช่น เรื่องราวที่น่าคลั่งไคล้ว่า ‘cabal’ ของโรคอัลไซเมอร์ขัดขวางความก้าวหน้าในการรักษามานานหลายทศวรรษ (ตั้งแต่ปี 2019) หรือ เรื่องราวของ Rachael Neve ผลสุทธิ? ไม่มีความคืบหน้าแม้จะมีเงินทุนจำนวนมาก:

    ในช่วง 30 (ปัจจุบัน 35) ปีที่นักวิจัยด้านชีวการแพทย์ได้ทำงานอย่างมุ่งมั่นเพื่อหาวิธีรักษาโรคอัลไซเมอร์ เพื่อนร่วมงานของพวกเขาได้พัฒนายาที่ช่วยลดการเสียชีวิตจากโรคหัวใจและหลอดเลือดได้มากกว่าครึ่งหนึ่ง และยารักษามะเร็งสามารถกำจัดเนื้องอกที่รักษาไม่หาย . แต่สำหรับโรคอัลไซเมอร์ ไม่เพียงแต่ไม่มีทางรักษา ไม่มีแม้แต่การรักษาที่ชะลอการเกิดโรคอีกด้วย

    เรามี การวิจัยเกี่ยวกับโรคอัลไซเมอร์เป็นเวลาสองทศวรรษ โดยส่วนหนึ่งมาจากการฉ้อโกงโดยเจตนา ซึ่งอาจมีค่าใช้จ่ายหลายล้านชีวิต

  4. เป็นเวลาหลายปีแล้วที่เราได้รับแจ้งว่าภาวะซึมเศร้าเกิดจากความไม่สมดุลของสารเคมีในสมอง โดยเฉพาะระดับเซโรโทนินที่ต่ำ เริ่มแรกเสนอให้ยาต้านอาการซึมเศร้าทำงานโดยแก้ไขความผิดปกติของเซโรโทนิน บทความใหม่ที่ตีพิมพ์โดย Nature แสดงให้เห็นว่าคนที่เป็นโรคซึมเศร้าไม่ได้มีระดับ serotonin ที่ต่ำกว่าเท่านั้น แต่ยิ่งไปกว่านั้น การใช้ยากล่อมประสาทในระยะยาวอาจทำให้ระดับ serotonin ลดลงได้
  5. วิทยาศาสตร์ได้รับการ ‘โควิด’ :

    ในสาขาวิทยาศาสตร์ 98 แห่งจาก 100 เอกสารที่ตีพิมพ์มากที่สุดในปี 2020 ถึง 2021 เกี่ยวข้องกับ COVID-19 นักวิทยาศาสตร์จำนวนมากได้รับการอ้างอิงงานเกี่ยวกับโควิด-19 เป็นจำนวนมาก ซึ่งมักจะเกินการอ้างอิงที่พวกเขาได้รับจากงานทั้งหมดตลอดอาชีพการทำงาน

  6. โลกกำลังเป็นมิตรกับสิ่งแวดล้อมมากขึ้นเนื่องจากการมีอยู่ของ CO2 ในชั้นบรรยากาศที่เพิ่มขึ้น และคาดว่าจะนำไปสู่การเย็นลงอย่างมากตาม บทความใน Nature
  7. ทะเลทรายซาฮาร่าเป็นสีเขียวตั้งแต่ 14,000 ถึง 5,000 ปีก่อน นักวิจัยบางคนเชื่อว่ามันกลายเป็นทะเลทรายเนื่องจากการเย็นตัวของมหาสมุทรแอตแลนติกอย่างกะทันหัน

เปรียบเทียบ strtod กับ from_chars (GCC 12)

ผู้อ่าน (Richard Ebeling) เชิญให้ฉันทบทวนบล็อกโพสต์ที่เก่ากว่า: Parsing floats in C++: การเปรียบเทียบ strtod vs. from_chars ย้อนกลับไปแล้ว ฉันรายงานว่าการเปลี่ยนจาก strtod เป็น from_chars ใน C++ เป็นหมายเลขการแยกวิเคราะห์อาจทำให้ความเร็วเพิ่มขึ้น (เพิ่มขึ้น 20%) รหัสเหมือนกันมาก เราไปจาก…

 ถ่าน * string = " 3.1416 " ; ถ่าน * string_end = สตริง ; double x = strtod ( สตริง , & string_end ) ; ถ้า ( string_end = = สตริง ) {    // คุณมีข้อผิดพลาด! } 

… สู่ความทันสมัยใน C++17…

 std :: สตริง st = " 3.1416 " ; สองเท่า x ;  อัตโนมัติ [ p , ec ] = std :: from_chars ( st . data ( ) , st . data ( ) + st . ขนาด ( ) , x ) ; ถ้า ( p = = st . data ( ) ) {       // คุณมีข้อผิดพลาด s! } 

ย้อนกลับไปเมื่อฉันรายงานผลลัพธ์นี้ครั้งแรก มีเพียง Visual Studio เท่านั้นที่รองรับ from_chars ขณะนี้ไลบรารี C++ ใน GCC 12 รองรับ from_chars อย่างเต็มรูปแบบแล้ว ให้เราเรียกใช้การวัดประสิทธิภาพอีกครั้ง :

strtod 270 MB/วินาที
from_chars 1 GB/วินาที

เร็วกว่าเกือบสี่เท่า! เกณฑ์มาตรฐานจะอ่านค่าแบบสุ่มในช่วง [0,1]

ภายใน GCC 12 นำ ไลบรารี fast_float มาใช้

อ่านเพิ่มเติม : Number Parsing at a Gigabyte per Second , Software: Pratice and Experience 51 (8), 2021.

ปัดเศษเวกเตอร์ทิศทางเป็นเข็มทิศ 8 ทิศทาง

ภาพบุคคล2018facebook.jpg

ตัวควบคุมเกมสมัยใหม่สามารถชี้ไปในทิศทางที่หลากหลาย บางครั้งนักออกแบบเกมต้องการ เปลี่ยนทิศทางของจอยสติ๊กเพื่อให้ได้การเคลื่อนไหว 8 ทิศทาง วิธีแก้ปัญหาทั่วไปที่นำเสนอคือการคำนวณมุม ปัดเศษขึ้น แล้วคำนวณเวกเตอร์ทิศทางกลับ

 มุม คู่ = atan2 ( y , x ) ;  
   
  มุม = ( int ( รอบ ( 4 * มุม / PI + 8 ) ) % 8 ) * PI / 4 ;  
   
  xout = cos ( มุม ) ;  
   
  yout = บาป ( มุม ) ;  
   

หากคุณถือว่าเวกเตอร์ทิศทางอยู่ในจตุภาคแรก (ทั้ง x และ y เป็นบวก) แสดงว่ามีวิธีคำนวณคำตอบโดยตรง ใช้ 1/sqrt(2) หรือ 0.7071 เป็นโซลูชันเริ่มต้น เปรียบเทียบทั้ง x และ y กับ cos(3*pi/8) และ cos(pi/8) และเปลี่ยนเป็น 1 หรือ 0 หากมีค่ามากกว่า cos เท่านั้น (3*pi/8) หรือเล็กกว่า cos(pi/8) รหัสเต็มมีลักษณะดังนี้:

 xout = 0.7071067811865475 ;  
   
  คุณ = 0.7071067811865475 ;  
   
  
   
  ถ้า ( x > = 0.923879532511286 ) { // cos(3*pi/8)  
   
    xout = 1 ;  
   
  }  
   
  ถ้า ( y > = 0.923879532511286 ) { // cos(3*pi/8)  
   
    คุณ = 1 ;  
   
  }  
   
  ถ้า ( x < 0.3826834323650898 ) { // cos (pi/8)  
   
    xout = 0 ;  
   
  }  
   
  ถ้า ( y < 0.3826834323650898 ) { // cos (pi/8)  
   
    คุณ = 0 ;  
   
  }  
   

คุณสามารถสรุปวิธีแก้ปัญหาสำหรับกรณีที่ x หรือ y (หรือทั้งสองอย่าง) เป็นลบโดยเอาค่าสัมบูรณ์ก่อนแล้วจึงคืนค่าเครื่องหมายในตอนท้าย:

 บูล xneg = x < 0 ;  
   
  บูลเน็ก = y < 0 ;  
   
  ถ้า ( xneg ) {  
   
    x = - x ;  
   
  }  
   
  ถ้า ( เหน ) {  
   
    y = - y ;  
   
  }  
   
  outx สองเท่า = 0.7071067811865475 ;  
   
  ดับเบิ้ล = 0.7071067811865475 ;  
   
  
   
  ถ้า ( x > = 0.923879532511286 ) { // cos(3*pi/8)  
   
    outx = 1 ;  
   
  }  
   
  ถ้า ( y > = 0.923879532511286 ) { // cos(3*pi/8)  
   
    นอก = 1 ;  
   
  }  
   
  ถ้า ( x < 0.3826834323650898 ) { // cos (pi/8)  
   
    outx = 0 ;  
   
  }  
   
  ถ้า ( y < 0.3826834323650898 ) { // cos (pi/8)  
   
    นอก = 0 ;  
   
  }  
   
  ถ้า ( xneg ) {  
   
    outx = - outx ;  
   
  }  
   
  ถ้า ( เหน ) {  
   
    outy = - outy ;  
   
  }  
   

ฉันเขียนเกณฑ์มาตรฐานขนาดเล็กที่ทำงานบนอินพุตแบบสุ่ม ผลลัพธ์ของคุณจะแตกต่างกันไป แต่ในแล็ปท็อป Mac ของฉันที่มี LLVM 12 ฉันเข้าใจว่าวิธีการโดยตรงนั้นเร็วกว่า 25 เท่า

ด้วยแทนเจนต์ 40 ns/เวกเตอร์
วิธีที่รวดเร็ว 1.5 ns/เวกเตอร์

ลิงค์วิทยาศาสตร์และเทคโนโลยี (23 กรกฎาคม 2565)

ภาพบุคคล2018facebook.jpg

    1. เมื่อเทียบกับปี 1800 เรากินไขมันอิ่มตัวน้อยลง อาหารแปรรูปและน้ำมันพืชมากกว่า และดูเหมือนจะไม่ดีสำหรับเรา:

      ไขมันอิ่มตัวจากแหล่งสัตว์ลดลงในขณะที่ไขมันไม่อิ่มตัวเชิงซ้อนจากน้ำมันพืชเพิ่มขึ้น โรคไม่ติดต่อ (NCDs) เพิ่มขึ้นในศตวรรษที่ 20 ควบคู่ไปกับการบริโภคอาหารแปรรูปที่เพิ่มขึ้น ซึ่งรวมถึงน้ำตาล แป้งกลั่นและข้าว และน้ำมันพืช ไขมันอิ่มตัวจากสัตว์มีความสัมพันธ์ผกผันกับความชุกของโรคไม่ติดต่อ

      คัง et al. พบว่าไขมันอิ่มตัวลดความเสี่ยงของการเป็นโรคหลอดเลือดสมอง:

      การบริโภคอาหารที่มีไขมันอิ่มตัวสูงนั้นสัมพันธ์กับความเสี่ยงที่ลดลงของโรคหลอดเลือดสมอง และทุกๆ 10 กรัม/วันของการบริโภคไขมันอิ่มตัวที่เพิ่มขึ้นนั้นสัมพันธ์กับการลดความเสี่ยงที่สัมพันธ์กัน 6% ของอัตราการเกิดโรคหลอดเลือดสมอง

      ไขมันอิ่มตัวมาจากเนื้อสัตว์และผลิตภัณฑ์จากนม (เช่น เนย) การรับประทานอาหารที่มีไขมันต่ำสามารถเพิ่มความเสี่ยงของการเกิดโรคหลอดเลือดหัวใจได้อย่างมีนัยสำคัญ
      Leroy และ Cofnas โต้เถียงกับการลดการบริโภคเนื้อแดง:

      IARC’s (2015) อ้างว่าเนื้อแดง “อาจเป็นสารก่อมะเร็ง” ไม่เคยได้รับการพิสูจน์มาก่อน อันที่จริง การประเมินความเสี่ยงโดยครูเกอร์และโจว (2018) สรุปว่าไม่เป็นเช่นนั้น (…) การวิเคราะห์เมตาของ RCT แสดงให้เห็นว่าการกินเนื้อสัตว์ไม่ได้นำไปสู่การเสื่อมสภาพของตัวบ่งชี้ความเสี่ยงโรคหัวใจและหลอดเลือด (O’Connor et al., 2017) หมวดที่กินเนื้อสัตว์สูงที่สุดนั้นควบคู่ไปกับการเพิ่มขึ้นของระดับ HDL-C ที่อาจเป็นประโยชน์ ในขณะที่อาหารที่เน้นพืชเป็นหลักดูเหมือนจะลดโคเลสเตอรอลรวมและ LDL-C ในการศึกษาการแทรกแซง พวกเขายังเพิ่มระดับไตรกลีเซอไรด์และลด HDL-C (Yokoyama et al., 2017) ซึ่งปัจจุบันมักถูกมองว่าเป็นตัวบ่งชี้ที่เหนือกว่าของความเสี่ยงโรคหัวใจและหลอดเลือด ( Jeppesen et al., 2001). (…) เราเชื่อว่าการบริโภคเนื้อสัตว์ที่ลดลงอย่างมาก เช่น ได้รับการสนับสนุนจากคณะกรรมาธิการ EAT-Lancet (Willett et al., 2019) อาจก่อให้เกิดอันตรายร้ายแรงได้ เนื้อสัตว์เป็นแหล่งโภชนาการคุณภาพสูงมาช้านานและยังคงดำเนินต่อไป ทฤษฎีที่ว่าพืชตระกูลถั่วและอาหารเสริมสามารถถูกแทนที่ได้เป็นเพียงการเก็งกำไร ในขณะที่อาหารที่มีเนื้อสัตว์สูงได้รับการพิสูจน์แล้วว่าประสบผลสำเร็จตลอดประวัติศาสตร์อันยาวนานของสายพันธุ์ของเรา ประโยชน์ของอาหารมังสวิรัตินั้นยังห่างไกลจากการเป็นที่ยอมรับ และผู้ที่รับรองก่อนเวลาอันควรเนื่องจากหลักฐานที่น่าสงสัยส่วนใหญ่ละเลยอันตรายของอาหารเหล่านี้

    2. ผู้คนไม่ชอบงานวิจัยที่ดูเหมือนชอบผู้ชาย :

      ในการศึกษาทั้งสอง ทั้งสองเพศมีปฏิกิริยาในเชิงบวกน้อยลงต่อความแตกต่างที่ชื่นชอบผู้ชาย ตรงกันข้ามกับการวิจัยก่อนหน้านี้ของเรา อย่างไรก็ตาม ผลที่ได้นั้นมากกว่าในหมู่ผู้เข้าร่วมเพศหญิง ตรงกันข้ามกับความคาดหวังที่แพร่หลาย ผู้เข้าร่วมไม่ได้ตอบสนองในเชิงบวกน้อยลงต่อการวิจัยที่นำโดยผู้หญิง ผู้เข้าร่วมการวิจัยมีปฏิกิริยาในเชิงบวกน้อยลง ต่อการวิจัยที่นำโดยผู้ชายเมื่อการวิจัยรายงานความแตกต่างที่ผู้ชายชื่นชอบในลักษณะที่มีมูลค่าสูง ผู้เข้าร่วมประเมินว่างานวิจัยที่ถูกใจผู้ชายนั้นมีคุณภาพต่ำกว่างานวิจัยที่ถูกใจผู้หญิง ซึ่งส่วนใหญ่เห็นได้ชัดเพราะพวกเขามองว่าการวิจัยแบบเดิมนั้นเป็นอันตรายมากกว่า

    3. ในช่วงยุคจูราสสิก คาร์บอนไดออกไซด์ในชั้นบรรยากาศสูงมาก ป่าไม้แผ่ขยายไปจนถึงขั้วโลกเหนือ ถึงกระนั้นก็มีฤดูหนาวที่หนาวเหน็บ :

      ป่าไม้มีอยู่ตลอดทางจนถึงขั้วโลกเหนือของแพงเจียน และเข้าไปในละติจูดใต้สุดเท่าที่แผ่นดินขยายออกไป แม้ว่าอาจมีปัจจัยสนับสนุนอื่น ๆ แต่สมมติฐานชั้นนำก็คือโลกอยู่ในสถานะ “เรือนกระจก” เนื่องจากมี PCO2 ในชั้นบรรยากาศที่สูงมาก (ความดันบางส่วนของ CO2) ซึ่งสูงที่สุดในรอบ 420 ล้านปีที่ผ่านมา แม้ว่าผลการสร้างแบบจำลองจะบ่งชี้ว่าอุณหภูมิในฤดูหนาวเยือกแข็งที่ละติจูดสูง แต่ยังไม่มีหลักฐานเชิงประจักษ์สำหรับการแช่แข็ง ในที่นี้ เราให้หลักฐานเชิงประจักษ์ที่แสดงให้เห็นว่า แม้จะมี PCO2 ที่สูงเป็นพิเศษ แต่อุณหภูมิเยือกแข็งในฤดูหนาวก็แสดงให้เห็นลักษณะละติจูดของ Pangean ที่สูงโดยอิงจากเศษซากที่ล่องแก่งด้วยน้ำแข็งในทะเลสาบ (L-IRD) ที่แพร่หลายในชั้นหินในชั้นหินมีโซโซอิกช่วงต้นของลุ่มน้ำ Junggar ทางตะวันตกเฉียงเหนือของจีน ตามเนื้อผ้า ไดโนเสาร์ถูกมองว่าเฟื่องฟูในภูมิอากาศแบบมีโซโซอิกช่วงต้นที่อบอุ่นและเท่าเทียมกัน แต่ผลลัพธ์ของเราบ่งชี้ว่าพวกมันสามารถทนต่อฤดูหนาวที่หนาวเหน็บได้เช่นกัน

    4. ในหนูทดลอง นักวิจัยพบ ว่าการฉีด ” สื่อที่ปรับสภาพ ” ที่ได้จากสเต็มเซลล์ช่วยป้องกันการเสื่อมของระบบประสาท:

      การตายของเซลล์ประสาทมีสาเหตุมาจากโรคทางระบบประสาทหลายชนิด รวมถึงการสูญเสียความจำและภาวะสมองเสื่อมที่เกี่ยวข้องกับอายุ (เช่น โรคอัลไซเมอร์) โรคพาร์กินสัน โรคหลอดเลือดสมอง และโรคที่ส่งผลกระทบต่อวัยทั่วไป เช่น การบาดเจ็บที่สมอง การบาดเจ็บที่ไขสันหลัง ALS และกล้ามเนื้อลีบกระดูกสันหลัง โรคเหล่านี้มีลักษณะเฉพาะจากการอักเสบของเส้นประสาทและความเสียหายของเซลล์ออกซิเดชัน หลายชนิดเกี่ยวข้องกับโปรตีโอสตาซิสที่ถูกรบกวน ซึ่งล้วนแต่ทำลายล้างและไม่มีวิธีรักษา งานของเราอธิบายถึงการรักษาโรค ALS ที่มีความหมายที่เป็นไปได้ให้น้อยที่สุด และแสดงให้เห็นความสามารถทางคลินิกในการรักษาโรคต่างๆ ที่เกี่ยวข้องกับการเสื่อมสภาพของระบบประสาท และการตายของเซลล์ที่มากเกินไป

    5. กรีนแลนด์และทางเหนือของยุโรปเคยอบอุ่นกว่าในทุกวันนี้: ยุคอบอุ่น ในยุคกลาง (950 ถึง 1250) ซ้อนทับกับยุคไวกิ้ง (800–1300) Bajard และคณะ (2022) เสนอว่าไวกิ้งค่อนข้างเชี่ยวชาญในการปรับวิธีปฏิบัติทางการเกษตรของตน:

      (…) ช่วงเวลาตั้งแต่ยุคไวกิ้งจนถึงยุคกลางสูงเป็นช่วงเวลาของการขยายตัวพร้อมกับชาวไวกิ้งพลัดถิ่น เพิ่มการค้า การผลิตอาหารและสินค้า และการก่อตั้งเมืองสแกนดิเนเวีย ช่วงเวลานี้ยังเห็นการเพิ่มขึ้นอย่างรวดเร็วของประชากรและการตั้งถิ่นฐาน สาเหตุหลักมาจากสภาพอากาศที่อบอุ่นค่อนข้างคงที่ (…) เป็นปัจจัยขับเคลื่อนหลักในการปฏิบัติทางการเกษตรในนอร์เวย์ตะวันออกเฉียงใต้ในสมัยโบราณตอนปลาย การเปรียบเทียบโดยตรงระหว่างความแปรปรวนของอุณหภูมิที่สร้างขึ้นใหม่และข้อมูล palynological จากลำดับตะกอนเดียวกันแสดงให้เห็นว่าการเปลี่ยนแปลงเล็กน้อยของอุณหภูมิสอดคล้องกับการเปลี่ยนแปลงในการเกษตร (…) เราสรุปได้ว่าสังคมยุคก่อนยุคไวกิ้งในสแกนดิเนเวียตะวันตกเฉียงใต้ได้ทำการเปลี่ยนแปลงอย่างมากในแนวทางของพวกเขา การใช้ชีวิตเพื่อปรับตัวให้เข้ากับสภาพอากาศแปรปรวนในช่วงนี้

      ชาวไวกิ้งปลูกข้าวบาร์เลย์และข้าวโพดในกรีนแลนด์ ในทางตรงกันข้าม เกษตรกรรมในกรีนแลนด์ในปัจจุบันแทบไม่มีเลยเนื่องจากสภาพอากาศที่เลวร้าย

    6. หน่วยสืบราชการลับของอาซเกนาซีมักจะทำคะแนนได้ดีเป็นพิเศษในการทดสอบสติปัญญา และพวกเขาบรรลุผลลัพธ์ที่ไม่ธรรมดาในการแสวงหาทางปัญญาหลายอย่าง
      อย่างไรก็ตาม บรรณาธิการวิกิพีเดียได้ลบบทความเกี่ยวกับข่าวกรองอาซเกนาซี Tezuka โต้แย้งว่าเป็น ผลมาจากอคติทางอุดมการณ์ ที่ส่งผลให้เกิดการเซ็นเซอร์อย่างเป็นระบบ
    7. คุณสามารถชุบตัวผิวหนังคนแก่ได้ด้วยการทาบลงบนหนูตัว น้อย
    8. Tabarrok เตือนเราว่าทุนวิจัยผ่านการแข่งขัน อาจส่งผลให้เกิดการสูญเสียทั้งหมดจากการกระจายค่าเช่า …

      นักวิทยาศาสตร์ที่ได้รับประโยชน์จากเงินช่วยเหลือ NIH มูลค่า 2 ล้านดอลลาร์ เต็มใจที่จะใช้เวลาหนึ่งล้านดอลลาร์ไปกับการทำงานกับแอปพลิเคชัน หรือต้องเสียค่าใช้จ่ายในการจำกัดแนวคิดการวิจัยเพื่อให้ได้มา ที่สำคัญ แม้ว่าจะมีนักวิทยาศาสตร์เพียงคนเดียวเท่านั้นที่จะได้รับทุน แต่นักวิทยาศาสตร์หลายร้อยคนก็ใช้ทรัพยากรในการแข่งขันเพื่อให้ได้มา ดังนั้น ประโยชน์ที่เราอาจได้รับจากการถ่ายโอนทรัพยากรไปยังนักวิจัยคนหนึ่งจึงกระจายไปทวีคูณในนักวิทยาศาสตร์ทุกคนที่ใช้เวลาและเงินแข่งขันกันเพื่อขอรับทุนแต่ไม่ได้รับ เวลาโดยรวมที่ใช้สำหรับความคิดที่เฉียบแหลมที่สุดของเราจากระบบการแข่งขันแอปพลิเคชันนี้มีขนาดใหญ่พอสมควร ซึ่งอาจหักล้างมูลค่าทางวิทยาศาสตร์ทั้งหมดของการวิจัยที่เงินทุนสนับสนุนได้ทั้งหมด

    9. การลดภาษีนิติบุคคลนำไปสู่การเพิ่มผลผลิตและการวิจัยในระยะยาว ในทางกลับกัน การเพิ่มขึ้นของการเก็บภาษีจะลดประสิทธิภาพการทำงานในระยะยาว เช่นเดียวกับการวิจัยและพัฒนา

คุณสามารถแปลงโฟลตเป็นสองเท่า (และย้อนกลับ) ได้เร็วแค่ไหน?

ภาษาโปรแกรมหลายภาษามีเลขทศนิยมสองประเภท: float (32 บิต) และคู่ (64 บิต) สะท้อนให้เห็นถึงความจริงที่ว่าโปรเซสเซอร์ที่ใช้งานทั่วไปส่วนใหญ่รองรับข้อมูลทั้งสองประเภทโดยกำเนิด

บ่อยครั้งที่เราต้องแปลงระหว่างสองประเภท โปรเซสเซอร์ทั้ง ARM และ x64 สามารถทำได้ในคำสั่งเดียวราคาไม่แพง ตัวอย่างเช่น ระบบ ARM อาจใช้คำสั่ง fcvt

รายละเอียดอาจแตกต่างกัน แต่โปรเซสเซอร์ปัจจุบันส่วนใหญ่สามารถแปลงตัวเลขได้หนึ่งหมายเลข (จาก float เป็น double หรือจาก double เป็น float) ต่อรอบ CPU เวลาแฝงมีขนาดเล็ก (เช่น 3 หรือ 4 รอบ)

โปรเซสเซอร์ทั่วไปอาจทำงานที่ 3 GHz ดังนั้นเราจึงมี 3 พันล้านรอบต่อวินาที ดังนั้นเราสามารถแปลงตัวเลข 3 พันล้านต่อวินาที หมายเลข 64 บิตใช้ 8 ไบต์ ดังนั้นจึงมีอัตราความเร็ว 24 ไบต์ต่อวินาที

ดังนั้นจึงไม่น่าเป็นไปได้ที่การแปลงประเภทจะเป็นคอขวดของประสิทธิภาพโดยทั่วไป หากคุณต้องการวัดความเร็วในระบบของคุณเอง: ฉันได้เขียนเกณฑ์มาตรฐาน C++ ขนาดเล็ก

กรองตัวเลขได้เร็วขึ้นด้วย SVE บนโปรเซสเซอร์ Amazon Graviton 3

ภาพบุคคล2018facebook.jpg

โปรเซสเซอร์มาในตระกูลใหญ่สองตระกูล x64 โปรเซสเซอร์จาก Intel และ AMD และโปรเซสเซอร์ ARM จาก Apple, Samsung และผู้จำหน่ายรายอื่น ๆ เป็นเวลานาน โปรเซสเซอร์ ARM ครอบครองตลาดของโปรเซสเซอร์แบบฝังเป็นส่วนใหญ่ (คอมพิวเตอร์ที่ใช้งานตู้เย็นของคุณที่บ้าน) โดยที่ ‘โปรเซสเซอร์ขนาดใหญ่’ เป็นโดเมนของโปรเซสเซอร์ x64 เท่านั้น

มีรายงานว่า CEO ของ Apple (Steve Jobs) ได้ไปพบ Intel ย้อนกลับไปเมื่อ Apple กำลังออกแบบ iPhone เพื่อขอข้อตกลงโปรเซสเซอร์ Intel ปฏิเสธ Apple ดังนั้น Apple จึงเลือกใช้ ARM

ทุกวันนี้ เราใช้โปรเซสเซอร์ ARM สำหรับทุกสิ่ง: เกมคอนโซล (Nintendo Switch), เซิร์ฟเวอร์ที่ทรงพลัง (Amazon และ Google), โทรศัพท์มือถือ, อุปกรณ์ฝังตัว และอื่นๆ

Amazon เปิดตัวโปรเซสเซอร์ที่ใช้ ARM รุ่นใหม่ (Graviton 3) โปรเซสเซอร์เหล่านี้มีคำสั่ง SIMD ที่ซับซ้อน (SIMD ย่อมาจาก Single Instruction Multiple Data) ที่เรียกว่า SVE (Scalable Vector Extensions) ด้วยคำแนะนำเหล่านี้ เราสามารถเร่งความเร็วซอฟต์แวร์ได้อย่างมาก เป็นรูปแบบหนึ่งของ single-core parallelism เมื่อเทียบกับความขนานที่ได้รับโดยใช้หลายคอร์สำหรับหนึ่งงาน เมื่อนำไปใช้ได้ SIMD parallelism มักจะมีประสิทธิภาพมากกว่า multicore parallelism

Graviton 3 ของ Amazon ดูเหมือนจะมีการลงทะเบียน 32 ไบต์ เนื่องจากดีที่สุดสำหรับการ ออกแบบ ARM Neoverse V1 คุณสามารถใส่จำนวนเต็ม 32 บิตแปดตัวในการลงทะเบียนเดียว โปรเซสเซอร์ ARM หลัก (เช่นตัวที่ Intel ใช้) มีคำสั่ง SIMD ด้วย (NEON) แต่มีการลงทะเบียนที่สั้นกว่า (16 ไบต์) การมีรีจิสเตอร์และคำสั่งที่กว้างขึ้นซึ่งสามารถทำงานได้บนรีจิสเตอร์แบบกว้างเหล่านี้ ช่วยให้คุณลดจำนวนคำสั่งทั้งหมดได้ การรันคำสั่งน้อยลงเป็นวิธีที่ดีมากในการเร่งโค้ด

ในการตรวจสอบ SVE ฉันได้พิจารณาปัญหาง่ายๆ ที่คุณต้องการลบจำนวนเต็มลบทั้งหมดออกจากอาร์เรย์ นั่นคือ คุณอ่านและอาร์เรย์ที่มีจำนวนเต็มสุ่มที่ลงนามแล้ว และคุณต้องการเขียนลงในอาร์เรย์เอาต์พุตเฉพาะจำนวนเต็มบวกเท่านั้น รหัส C ปกติอาจมีลักษณะดังนี้:

 เป็นโมฆะ remove_negatives_scalar ( อินพุต const int32_t *           จำนวน int64_t , int32_t * เอาต์พุต ) {    int64_t ผม = 0 ;    int64_t j = 0 ;    สำหรับ ( ; ผม < นับ ; ผม + + ) {      ถ้า ( ป้อน [ i ] > = 0 ) {        เอาท์พุต [ j + + ] = อินพุต [ i ] ;      }    }  }  

การแทนที่โค้ดนี้ด้วยโค้ดใหม่ที่อาศัยฟังก์ชัน SVE พิเศษ ทำให้ใช้งานได้เร็วขึ้นมาก (เร็วขึ้น 2.5 เท่า) ในขณะนั้น ฉันแนะนำว่าโค้ดของฉันอาจไม่ค่อยเหมาะสมที่สุด มันประมวลผล 32 ไบต์ต่อการวนซ้ำโดยใช้ 9 คำแนะนำ ส่วนที่ใหญ่มากของคำแนะนำทั้ง 9 ข้อนี้เกี่ยวข้องกับการจัดการลูป และมีเพียงไม่กี่คำเท่านั้นที่ทำการกระทืบตัวเลขจริง ผู้อ่านชื่อ Samuel Lee เสนอให้คลายลูปของฉันอย่างมีประสิทธิภาพ เขาคาดการณ์ถึงประสิทธิภาพที่ดีขึ้นมาก (อย่างน้อยเมื่ออาร์เรย์มีขนาดใหญ่พอ) เนื่องจากโอเวอร์เฮดของลูปที่ต่ำกว่า ฉันรวมรหัสที่เขาเสนอไว้ด้านล่าง

เมื่อใช้โปรเซสเซอร์ graviton 3 และ GCC 11 ในเกณฑ์มาตรฐาน ฉันได้ผลลัพธ์ดังต่อไปนี้:

รอบ/int instr./int instr./cycle
สเกลาร์ 9.0 6.000 0.7
สเกลาร์ไร้แขนง 1.8 8.000 4.4
SVE 0.7 1.125 ~1.6
คลี่ SVE 0.4385 0.71962 ~1.6

โค้ด SVE ที่คลายใหม่ใช้คำสั่ง 23 คำสั่งในการประมวลผล 128 ไบต์ (หรือจำนวนเต็ม 32 บิต 32 บิต) ดังนั้นประมาณ 0.71875 คำสั่งต่อจำนวนเต็ม นั่นคือคำสั่งที่น้อยกว่าโค้ดสเกลาร์ประมาณ 10 เท่า และเร็วกว่าโค้ดสเกลาร์ประมาณ 4 เท่าในแง่ของรอบของ CPU

จำนวนคำสั่งที่เลิกใช้ต่อรอบจะใกล้เคียงกันสำหรับฟังก์ชัน SVE สองฟังก์ชัน และค่อนข้างต่ำ ซึ่งค่อนข้างสูงกว่า 1.5 คำสั่งที่เลิกใช้ต่อรอบ

บ่อยครั้งข้อโต้แย้งที่สนับสนุน SVE คือไม่ต้องใช้รหัสพิเศษเพื่อสิ้นสุดการประมวลผลส่วนท้าย นั่นคือ คุณสามารถประมวลผลอาร์เรย์ทั้งหมดได้โดยใช้คำสั่ง SVE แม้ว่าความยาวของอาร์เรย์จะไม่ถูกหารด้วยขนาดรีจิสเตอร์ (ที่นี่มี 8 จำนวนเต็ม) ฉันพบว่าโค้ดของ Lee น่าสนใจ เพราะมันแสดงให้เห็นว่าจริง ๆ แล้วคุณอาจต้องจัดการกับส่วนท้ายของ long array ให้แตกต่างออกไป ด้วยเหตุผลด้านประสิทธิภาพ

โดยรวมแล้ว ฉันคิดว่าเราจะเห็นว่า SVE ทำงานได้ดีสำหรับปัญหาที่มีอยู่ (การกรองจำนวนเต็ม 32 บิตออก)

ภาคผนวก : รหัสของซามูเอล ลี

 เป็นโมฆะ remove_negatives ( อินพุต const int32_t * , จำนวน int64_t , เอาต์พุต int32_t * ) {    int64_t j = 0 ;    const int32_t * endPtr = อินพุต + นับ ;    const uint64_t vl_u32 = svcntw ( ) ;      svbool_t all_mask = svptrue_b32 ( ) ;    ในขณะที่ ( อินพุต < = endPtr - ( 4 * vl_u32 ) )    {        svint32_t in0 = svld1_s32 ( all_mask , อินพุต + 0 * vl_u32 ) ;        svint32_t in1 = svld1_s32 ( all_mask , อินพุต + 1 * vl_u32 ) ;        svint32_t in2 = svld1_s32 ( all_mask , อินพุต + 2 * vl_u32 ) ;        svint32_t in3 = svld1_s32 ( all_mask , อินพุต + 3 * vl_u32 ) ;          svbool_t pos0 = svcmpge_n_s32 ( all_mask , in0 , 0 ) ;        svbool_t pos1 = svcmpge_n_s32 ( all_mask , in1 , 0 ) ;        svbool_t pos2 = svcmpge_n_s32 ( all_mask , in2 , 0 ) ;        svbool_t pos3 = svcmpge_n_s32 ( all_mask , in3 , 0 ) ;          in0 = svcompact_s32 ( pos0 , in0 ) ;        in1 = svcompact_s32 ( pos1 , in1 ) ;        in2 = svcompact_s32 ( pos2 , in2 ) ;        in3 = svcompact_s32 ( pos3 , in3 ) ;          svst1_s32 ( all_mask , เอาต์พุต + j , in0 ) ;        j + = svcntp_b32 ( all_mask , pos0 ) ;        svst1_s32 ( all_mask , เอาต์พุต + j , in1 ) ;        j + = svcntp_b32 ( all_mask , pos1 ) ;        svst1_s32 ( all_mask , เอาต์พุต + j , in2 ) ;        j + = svcntp_b32 ( all_mask , pos2 ) ;        svst1_s32 ( all_mask , เอาต์พุต + j , in3 ) ;        j + = svcntp_b32 ( all_mask , pos3 ) ;          อินพุต + = 4 * vl_u32 ;    }      int64_t ผม = 0 ;    นับ = endPtr - อินพุต ;      svbool_t while_mask = svwhilelt_b32 ( ผม นับ ) ;    ทำ {      svint32_t ใน = svld1_s32 ( while_mask , input + i ) ;      svbool_t positive = svcmpge_n_s32 ( while_mask ใน 0 )      svint32_t in_positive = svcompact_s32 ( บวก ใน ) ;      svst1_s32 ( while_mask , เอาต์พุต + j , in_positive ) ;      ผม + = svcntw ( ) ;      j + = svcntp_b32 ( while_mask บวก ) ;      while_mask = svwhilelt_b32 ( ผม นับ ) ;    } ในขณะที่ ( svptest_any ( svptrue_b32 ( ) while_mask ) ) ;  }  

ยาสามัญไปก็ไม่เลว

ภาพบุคคล2018facebook.jpg

เมื่อเขียนโปรแกรม เรามักจะต้องเขียนฟังก์ชัน ‘ทั่วไป’ โดยที่ประเภทข้อมูลที่แน่นอนนั้นไม่สำคัญ ตัวอย่างเช่น คุณอาจต้องการเขียนฟังก์ชันง่ายๆ ที่รวมตัวเลข

Go ขาดแนวคิดนี้จนกระทั่งเมื่อไม่นานมานี้ แต่เพิ่งถูกเพิ่มเข้ามา (ในเวอร์ชัน 1.18) ฉันก็เลยเอามันออกไปปั่น

ใน Java ยาชื่อสามัญจะทำงานได้ดีตราบเท่าที่คุณต้องการคอนเทนเนอร์ “ทั่วไป” (อาร์เรย์ แผนที่) และตราบเท่าที่คุณยังคงใช้สำนวนที่ใช้งานได้ แต่ Java จะไม่ให้ฉันเขียนโค้ดในแบบที่ฉันต้องการ นี่คือวิธีที่ฉันจะเขียนฟังก์ชันที่รวมตัวเลข:

 ผลรวม int ( int [ ] v ) {          int ฤดูร้อน = 0 ;          สำหรับ ( int k = 0 ; k < v . length ; k + + ) {              ฤดูร้อน + = v [ k ] ;          }          กลับ ฤดูร้อน ;      }  

จะเกิดอะไรขึ้นหากฉันต้องการสนับสนุนตัวเลขประเภทต่างๆ? ฉันต้องการเขียนฟังก์ชันทั่วไปต่อไปนี้ แต่ Java ไม่ยอมให้ฉัน

 // รหัส Java นี้จะไม่คอมไพล์      คงที่ < T ขยายจำนวน > T ผลรวม ( T [ ] v ) {          T ฤดูร้อน = 0 ;          สำหรับ ( int k = 0 ; k < v . length ; k + + ) {              ฤดูร้อน + = v [ k ] ;          }          กลับ ฤดูร้อน ;      }  

Go ไม่ใช่เชิงวัตถุ ดังนั้นคุณจึงไม่มีคลาส ‘Number’ อย่างไรก็ตาม คุณสามารถสร้าง ‘อินเทอร์เฟซ’ ทั่วไปของคุณเองซึ่งทำหน้าที่เดียวกันได้ นี่คือวิธีแก้ปัญหาเดียวกันใน Go:

 พิมพ์หมายเลขอินเทอร์เฟซ {    uint | int | float32 | float64  }      func sum [ T หมายเลข ] ( a [ ] T ) T {      var ฤดูร้อน T      สำหรับ _ , v : = ช่วง ( a ) {          ฤดูร้อน + = v      }     กลับ ฤดูร้อน  }    

อย่างน้อยในกรณีนี้ Go generics มีความหมายมากกว่า Java generics แล้วประสิทธิภาพล่ะ?

ถ้าฉันใช้โค้ดข้างต้นกับอาร์เรย์ของจำนวนเต็ม ฉันได้รับลูปแน่นต่อไปนี้ในแอสเซมบลี:

 พีซี11:          MOVQ ( AX ) ( DX * 8 ) , SI          INCQ DX          ADDQ SI , CX          CMPQ BX , DX          JGT pc11  

เท่าที่เกี่ยวข้องกับ Go สิ่งนี้มีประสิทธิภาพตามที่ได้รับ

จนถึงตอนนี้ฉันกำลังให้ยาสามัญ A to Go

ดูรหัสแอสเซมบลีด้วย gdb

ภาพบุคคล2018facebook.jpg

พวกเราส่วนใหญ่เขียนโค้ดโดยใช้ภาษาระดับสูงกว่า (Go, C++) แต่ถ้าคุณต้องการเข้าใจโค้ดที่มีความสำคัญต่อโปรเซสเซอร์ของคุณ คุณต้องดูที่เวอร์ชัน ‘แอสเซมบลี’ ของโค้ดของคุณ การประกอบเป็นเพียงชุดคำสั่ง

ในตอนแรก รหัสแอสเซมบลีดูน่ากลัว และฉันไม่สนับสนุนให้คุณเขียนโปรแกรมขนาดใหญ่ในแอสเซมบลี อย่างไรก็ตาม ด้วยการฝึกเพียงเล็กน้อย คุณสามารถเรียนรู้การนับคำสั่งและค้นหากิ่งก้านสาขาได้ สามารถช่วยให้คุณได้รับข้อมูลเชิงลึกเกี่ยวกับวิธีการทำงานของโปรแกรม ให้ฉันอธิบายสิ่งที่คุณสามารถเรียนรู้ได้โดยดูที่การชุมนุม ให้เราพิจารณารหัส C ++ ต่อไปนี้:

 ยาว f ( int x ) {      อาร์เรย์ ยาว [ ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 999 , 10 } ;      ส่งคืน อาร์เรย์ [ x ] ;  }    ยาว f2 ( int x ) {      อาร์เรย์ ยาว [ ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 999 , 10 } ;      ส่งคืน อาร์เรย์ [ x + 1 ] ;  }  

รหัสนี้มีอาร์เรย์ 80 ไบต์สองชุด แต่เหมือนกัน นี่เป็นที่มาของความกังวลหรือไม่? หากคุณดูรหัสแอสเซมบลีที่สร้างโดยคอมไพเลอร์ส่วนใหญ่ คุณจะพบว่าค่าคงที่ที่เหมือนกันทุกประการนั้น ‘บีบอัด’ โดยทั่วไป (เก็บไว้เพียงเวอร์ชันเดียว) ถ้าฉันคอมไพล์ฟังก์ชันทั้งสองนี้ด้วย gcc หรือคอมไพเลอร์ clang โดยใช้แฟล็ก -S ฉันจะเห็นการบีบอัดได้อย่างชัดเจนเพราะอาร์เรย์เกิดขึ้นเพียงครั้งเดียว:

 .ข้อความ  .ไฟล์ "f.cpp"  .globl _Z1fi // -- ฟังก์ชันเริ่มต้น _Z1fi  .p2align 2  .type _Z1fi , @ฟังก์ชั่น  _Z1fi: // @_Z1fi  .cfi_startproc  // %bb.0:  adrp x8, .L__const._Z2f2i.array  เพิ่ม x8, x8, :lo12:.L__const._Z2f2i.array  ldr x0, [x8, w0, sxtw #3]  ย้อนเวลา  .Lfunc_end0:  .size _Z1fi, .Lfunc_end0-_Z1fi  .cfi_endproc                                          // -- สิ้นสุดฟังก์ชัน  .globl _Z2f2i // -- ฟังก์ชันเริ่มต้น _Z2f2i  .p2align 2  .type _Z2f2i , @ฟังก์ชั่น  _Z2f2i: // @_Z2f2i  .cfi_startproc  // %bb.0:  adrp x8, .L__const._Z2f2i.array  เพิ่ม x8, x8, :lo12:.L__const._Z2f2i.array  เพิ่ม x8, x8, w0, sxtw #3  แอลดีอาร์ x0, [x8, #8]  ย้อนเวลา  .Lfunc_end1:  .size _Z2f2i, .Lfunc_end1-_Z2f2i  .cfi_endproc                                          // -- สิ้นสุดฟังก์ชัน  .type .L__const._Z2f2i.array, @object // @__const._Z2f2i.array  .section .rodata,"a", @progbits  .p2align 3  .L__const._Z2f2i.array:  .xword 1 // 0x1  .xword 2 // 0x2  .xword 3 // 0x3  .xword 4 // 0x4  .xword 5 // 0x5  .xword 6 // 0x6  .xword 7 // 0x7  .xword 8 // 0x8  .xword 999 // 0x3e7  .xword 10 // 0xa  .size .L__const._Z2f2i.array, 80    .ident "Ubuntu clang เวอร์ชัน 14.0.0-1ubuntu1"  .section ".note.GNU-stack","", @progbits  .addrsig  

อย่างไรก็ตาม หากคุณแก้ไขค่าคงที่แม้เพียงเล็กน้อย การบีบอัดนี้โดยทั่วไปจะไม่เกิดขึ้น (เช่น หากคุณพยายามผนวกค่าจำนวนเต็มหนึ่งค่าเข้ากับอาร์เรย์ใดค่าหนึ่ง โค้ดจะทำซ้ำอาร์เรย์ทั้งหมด)

ในการประเมินประสิทธิภาพของรูทีนโค้ด การโจมตีบรรทัดแรกของฉันคือการนับคำสั่งเสมอ รักษาทุกอย่างเหมือนเดิม ถ้าคุณสามารถเขียนโค้ดใหม่เพื่อให้สร้างคำสั่งน้อยลง ก็ควรจะเร็วขึ้น ฉันยังชอบที่จะมองเห็นการกระโดดแบบมีเงื่อนไขเพราะบ่อยครั้งที่โค้ดของคุณอาจประสบปัญหาได้ หากสาขานั้นคาดเดาได้ยาก

ง่ายต่อการแปลงทั้งชุดของฟังก์ชันเป็นแอสเซมบลี แต่จะใช้งานไม่ได้เมื่อโครงการของคุณใหญ่ขึ้น ภายใต้ Linux ‘ดีบักเกอร์’ มาตรฐาน ( gdb ) เป็นเครื่องมือที่ยอดเยี่ยมในการดูรหัสแอสเซมบลีที่ผลิตโดยคอมไพล์ ให้เราพิจารณาโพสต์บล็อกก่อนหน้าของฉัน การ กรองตัวเลขอย่างรวดเร็วด้วย SVE บนโปรเซสเซอร์ Amazon Graviton 3 ในบล็อกโพสต์นั้น ฉันนำเสนอฟังก์ชันต่างๆ ที่ฉันใช้ใน ไฟล์ C++ แบบสั้น เพื่อตรวจสอบผลลัพธ์ ฉันเพียงโหลดไบนารีที่คอมไพล์แล้วลงใน gdb :

 $ gdb ./filter  

จากนั้นฉันก็สามารถตรวจสอบฟังก์ชันต่างๆ… เช่น ฟังก์ชัน remove_negatives :

 (gdb) ตั้งค่าการพิมพ์ asm-demangle  (gdb) disas remove_negatives  ดัมพ์ของรหัสแอสเซมเบลอร์สำหรับฟังก์ชัน remove_negatives(int const*, long, int*):     0x00000000000022e4 <+0>: mov x4, #0x0 // #0     0x00000000000022e8 <+4>: mov x3, #0x0 // #0     0x00000000000022ec <+8>: cntw x6     0x00000000000022f0 <+12>: ในขณะที่ p0.s, xzr, x1     0x00000000000022f4 <+16>: ไม่ใช่     0x00000000000022f8 <+20>: ld1w {z0.s}, p0/z, [x0, x3, lsl #2]     0x00000000000022fc <+24>: cmpge p1.s, p0/z, z0.s, #0     0x0000000000002300 <+28>: ขนาดกะทัดรัด z0.s, p1, z0.s     0x0000000000002304 <+32>: st1w {z0.s}, p0, [x2, x4, lsl #2]     0x0000000000002308 <+36>: cntp x5, p0, p1.s     0x000000000000230c <+40>: เพิ่ม x3, x3, x6     0x0000000000002310 <+44>: เพิ่ม x4, x4, x5     0x0000000000002314 <+48>: ในขณะที่ p0.s, x3, x1     0x0000000000002318 <+52>: b.ne 0x22f8 <remove_negatives(int const*, long, int*)+20> // b.any     0x000000000000231c <+56>: ยกเลิกอีกครั้ง  สิ้นสุดการถ่ายโอนข้อมูลแอสเซมเบลอร์  

ที่ที่อยู่ 52 เรากลับไปที่ที่อยู่ 20 แบบมีเงื่อนไข ดังนั้นเราจึงมีคำสั่งทั้งหมด 9 คำสั่งในลูปหลักของเรา จากการวัดประสิทธิภาพของฉัน (ดูโพสต์บล็อกก่อนหน้า) ฉันใช้คำแนะนำ 1.125 ต่อคำ 32 บิต ซึ่งสอดคล้องกับการประมวลผลวนซ้ำ 8 คำ 32 บิต

อีกวิธีในการประเมินประสิทธิภาพคือการดูสาขา ให้เราแยกชิ้นส่วน remove_negatives_scalar ซึ่งเป็นฟังก์ชันสาขา:

 (gdb) disas remove_negatives_scalar  ดัมพ์ของรหัสแอสเซมเบลอร์สำหรับฟังก์ชัน remove_negatives_scalar(int const*, long, int*):     0x0000000000002320 <+0>: cmp x1, #0x0     0x0000000000002324 <+4>: b.le 0x234c <remove_negatives_scalar (int const*, ยาว, int*)+44>     0x0000000000002328 <+8>: เพิ่ม x4, x0, x1, lsl #2     0x000000000000232c <+12>: mov x3, #0x0 // #0     0x0000000000002330 <+16>: ldr w1, [x0]     0x0000000000002334 <+20>: เพิ่ม x0, x0, #0x4     0x0000000000002338 <+24>: tbnz w1, #31, 0x2344 <remove_negatives_scalar(int const*, ยาว, int*)+36>     0x000000000000233c <+28>: str w1, [x2, x3, lsl #2]     0x0000000000002340 <+32>: เพิ่ม x3, x3, #0x1     0x0000000000002344 <+36>: cmp x4, x0     0x0000000000002348 <+40>: b.ne 0x2330 <remove_negatives_scalar(int const*, long, int*)+16> // b.any     0x000000000000234c <+44>: ย้อนกลับ  สิ้นสุดการถ่ายโอนข้อมูลแอสเซมเบลอร์  

เราเห็นสาขาตามที่อยู่ 24 (คำสั่ง tbnz ) มันจะข้ามสองคำแนะนำถัดไปแบบมีเงื่อนไข เรามีฟังก์ชัน ‘branchless’ เทียบเท่าที่เรียกว่า remove_negatives_scalar_branchless ให้เราดูว่ามันไม่มีกิ่งก้านจริงหรือไม่:

 (gdb) disas remove_negatives_scalar_branchless  ดัมพ์ของรหัสแอสเซมเบลอร์สำหรับฟังก์ชัน remove_negatives_scalar_branchless(int const*, long, int*):     0x0000000000002350 <+0>: cmp x1, #0x0     0x0000000000002354 <+4>: b.le 0x237c <remove_negatives_scalar_branchless (int const*, ยาว, int*)+44>     0x0000000000002358 <+8>: เพิ่ม x4, x0, x1, lsl #2     0x000000000000235c <+12>: mov x3, #0x0 // #0     0x0000000000002360 <+16>: ldr w1, [x0], #4     0x0000000000002364 <+20>: str w1, [x2, x3, lsl #2]     0x0000000000002368 <+24>: หรือ x1, x1, #0x80000000     0x000000000000236c <+28>: lsr w1, w1, #31     0x0000000000002370 <+32>: เพิ่ม x3, x3, x1     0x0000000000002374 <+36>: cmp x0, x4     0x0000000000002378 <+40>: b.ne 0x2360 <remove_negatives_scalar_branchless(int const*, long, int*)+16> // b.any     0x000000000000237c <+44>: ยกเลิกอีกครั้ง  สิ้นสุดการถ่ายโอนข้อมูลแอสเซมเบลอร์  (จีดีบี)  

นอกเหนือจากการกระโดดแบบมีเงื่อนไขที่เกิดจากลูป (ที่อยู่ 40) แล้ว รหัสนั้นไม่มีสาขาอย่างแท้จริง

ในกรณีนี้ ด้วยไฟล์ไบนารีขนาดเล็กไฟล์เดียว ทำให้ง่ายต่อการค้นหาฟังก์ชันที่ฉันต้องการ จะเกิดอะไรขึ้นหากฉันโหลดไบนารีขนาดใหญ่พร้อมฟังก์ชันที่คอมไพล์แล้วมากมาย

ให้ฉันตรวจสอบ ไบนารีเบนช์มาร์กจากไลบรารี simdutf มีฟังก์ชันมากมาย แต่ให้เราสมมติฉันกำลังมองหาฟังก์ชันที่อาจตรวจสอบความถูกต้องของอินพุต UTF-8 ฉันสามารถใช้ ฟังก์ชันข้อมูล เพื่อค้นหาฟังก์ชันทั้งหมดที่ตรงกับรูปแบบที่กำหนด

 (gdb) ฟังก์ชันข้อมูล validate_utf8  ฟังก์ชันทั้งหมดที่ตรงกับนิพจน์ทั่วไป "validate_utf8":    สัญลักษณ์ที่ไม่ใช่การดีบัก:  0x000000000000012710 event_aggregate simdutf::benchmarks::BenchmarkBase::count_events<simdutf::benchmarks::Benchmark::run_validate_utf8(simdutf::implementation const&, unsigned long)::{lambda()#1}>::bench เกณฑ์มาตรฐาน::run_validate_utf8(simdutf::implementation const&, unsigned long)::{lambda()#1}, unsigned long) [clone .constprop.0]  0x000000000000012b54 simdutf::benchmarks::Benchmark::run_validate_utf8(simdutf::implementation const&, unsigned long)  0x00000000000018c90 simdutf::fallback::implementation::validate_utf8(char const*, unsigned long) const  0x0000000000001b540 simdutf::arm64::implementation::validate_utf8(char const*, unsigned long) const  0x0000000000001cd84 simdutf::validate_utf8 (อักขระ const*, ไม่ได้ลงนามแบบยาว)  0x0000000000001d7c0 simdutf::internal::unsupported_implementation::validate_utf8(char const*, unsigned long) const  0x0000000000001e090 simdutf::internal::detect_best_supported_implementation_on_first_use::validate_utf8(char const*, unsigned long) const  

คุณเห็นว่า ฟังก์ชัน info ให้ทั้งชื่อฟังก์ชันและที่อยู่ของฟังก์ชัน ฉันสนใจ simdutf::arm64::implementation::validate_utf8 เมื่อถึงจุดนั้น มันจะง่ายกว่าที่จะอ้างอิงถึงฟังก์ชันตามที่อยู่:

 (gdb) ความหายนะ 0x0000000000001b540  ดัมพ์ของรหัสแอสเซมเบลอร์สำหรับฟังก์ชัน simdutf::arm64::implementation::validate_utf8(char const*, unsigned long) const:     0x0000000000001b540 <+0>: stp x29, x30, [sp, #-144]!     0x0000000000001b544 <+4>: adrp x0, 0xa0000     0x0000000000001b548 <+8>: cmp x2, #0x40     0x0000000000001b54c <+12>: mov x29, sp     0x0000000000001b550 <+16>: ldr x0, [x0, #3880]     0x0000000000001b554 <+20>: mov x5, #0x40 // #64     0x0000000000001b558 <+24>: หนัง v22.4s, #0x0     0x0000000000001b55c <+28>: csel x5, x2, x5, cs // cs = hs, nlast     0x0000000000001b560 <+32>: ldr x3, [x0]     0x0000000000001b564 <+36>: str x3, [sp, #136]     0x0000000000001b568 <+40>: mov x3, #0x0 // #0     0x0000000000001b56c <+44>: ซับ x5, x5, #0x40     0x0000000000001b570 <+48>: b.eq 0x1b7b8 <simdutf::arm64::implementation::validate_utf8(char const*, unsigned long) const+632> // b.none     0x0000000000001b574 <+52>: adrp x0, 0x86000     0x0000000000001b578 <+56>: adrp x4, 0x86000     0x0000000000001b57c <+60>: เพิ่ม x6, x0, #0x2f0     0x0000000000001b580 <+64>: adrp x0, 0x86000  ...  

ฉันตัดเอาต์พุตให้สั้นเพราะมันยาวเกินไป เมื่อฟังก์ชันเดียวมีขนาดใหญ่ขึ้น ฉันพบว่าสะดวกกว่าที่จะเปลี่ยนเส้นทางเอาต์พุตไปยังไฟล์ที่ฉันสามารถประมวลผลที่อื่นได้

 gdb -q ./benchmark -ex "ปิดการแบ่งหน้า" -ex "ตั้งค่าการพิมพ์ asm-demangle" -ex "disas 0x0000000000001b540" -ex quit > gdbasm.txt  

บางครั้งฉันแค่สนใจที่จะทำสถิติพื้นฐานบางอย่าง เช่น การหาคำสั่งที่ฟังก์ชันใช้:

 $ gdb -q ./benchmark -ex "ปิดการแบ่งหน้า" -ex "set print asm-demangle" -ex "disas 0x0000000000001b540" -ex quit | awk '{พิมพ์ $3}' | sort |uniq -c | sort -r | ศีรษะ       32 และ       24 tbl       24 ต่อ       18 ซม       17 อ       16 ชั่วโมง       16 ปี       อายุ 14 ปี       13 mov       10 หนัง  

และเราเห็นว่าคำสั่งที่พบบ่อยที่สุดในโค้ดนี้คือ และ . มันทำให้ฉันมั่นใจว่าโค้ดได้รับการคอมไพล์อย่างถูกต้อง ฉันสามารถค้นคว้าเกี่ยวกับคำแนะนำที่สร้างขึ้นทั้งหมดได้ และดูเหมือนตัวเลือกที่เพียงพอเมื่อพิจารณาจากโค้ดที่ฉันสร้าง

บทเรียนทั่วไปคือ การดูแอสเซมบลีที่สร้างขึ้นนั้นไม่ใช่เรื่องยาก และด้วยการฝึกอบรมเพียงเล็กน้อย ก็สามารถทำให้คุณเป็นโปรแกรมเมอร์ที่ดีขึ้นได้

ความขนานระดับหน่วยความจำ : Intel Ice Lake กับ Amazon Graviton 3

หนึ่งในการดำเนินการที่แพงที่สุดในโปรเซสเซอร์และระบบหน่วยความจำคือการเข้าถึงหน่วยความจำแบบสุ่ม หากคุณพยายามอ่านค่าในหน่วยความจำ อาจใช้เวลาเฉลี่ยหลายสิบนาโนวินาทีหรือมากกว่านั้น หากคุณกำลังรอเนื้อหาหน่วยความจำเพื่อดำเนินการต่อไป โปรเซสเซอร์ของคุณจะหยุดทำงานอย่างมีประสิทธิภาพ แม้ว่าโดยทั่วไปแล้วโปรเซสเซอร์ของเราจะเร็วกว่า แต่เวลาแฝงของหน่วยความจำยังไม่ได้รับการปรับปรุงอย่างรวดเร็ว และเวลาแฝงอาจสูงขึ้นในโปรเซสเซอร์ที่มีราคาแพงที่สุดบางตัว ด้วยเหตุผลนี้ แกนประมวลผลสมัยใหม่จึงสามารถออกคำขอหน่วยความจำได้หลายรายการในเวลาที่กำหนด กล่าวคือ โปรเซสเซอร์พยายามโหลดองค์ประกอบหน่วยความจำหนึ่งตัว ทำงานต่อ และสามารถออกโหลดอื่นได้ (แม้ว่าการโหลดครั้งก่อนจะยังไม่เสร็จสิ้น) และอื่นๆ ไม่นานมานี้ คอร์โปรเซสเซอร์ Intel สามารถรองรับคำขอหน่วยความจำอิสระได้ประมาณ 10 รายการในแต่ละครั้ง ฉันเปรียบเทียบคอร์ ARM ขนาดเล็กบางตัวที่แทบจะไม่สามารถออกคำขอหน่วยความจำได้ 4 รายการ

วันนี้เรื่องราวดีขึ้นมาก คอร์โปรเซสเซอร์ที่ทรงพลังทั้งหมดสามารถรักษาคำขอหน่วยความจำได้มากมาย รองรับการ ทำงานคู่ขนานระดับหน่วยความจำ ที่ดีขึ้น

ในการวัดประสิทธิภาพของโปรเซสเซอร์ เราใช้รูปแบบการไล่ตามตัวชี้ โดยที่คุณขอให้โปรแกรม C โหลดที่อยู่หน่วยความจำซึ่งมีที่อยู่หน่วยความจำถัดไปและอื่นๆ หากโปรเซสเซอร์สามารถรักษาคำขอหน่วยความจำได้เพียงตัวเดียว การทดสอบดังกล่าวจะใช้ทรัพยากรที่มีอยู่ทั้งหมด จากนั้นเราแก้ไขการทดสอบนี้เพื่อให้เรามีรูปแบบการไล่ตามตัวชี้แบบแทรกสลับสองรูปแบบ และจากนั้นสามและสี่ และอื่นๆ เราเรียกองค์ประกอบการไล่ตามพอยน์เตอร์แบบอินเตอร์ลีฟใหม่ว่า ‘เลน’

เมื่อคุณเพิ่มช่องทางมากขึ้น คุณจะเห็นประสิทธิภาพที่ดีขึ้น สูงสุด ยิ่งประสิทธิภาพเพิ่มขึ้นเร็วขึ้นเมื่อคุณเพิ่มเลน ยิ่งคอร์โปรเซสเซอร์ของคุณมีความขนานระดับหน่วยความจำมากขึ้นเท่านั้น เซิร์ฟเวอร์ Amazon (AWS) ที่ดีที่สุดมาพร้อมกับ Intel Ice Lake หรือ Graviton 3 ของ Amazon เอง ฉันเปรียบเทียบทั้งสองอย่างโดยใช้แกนหลักของแต่ละประเภท โปรเซสเซอร์ Intel มีความเหนือกว่าในแง่ที่แน่นอน เรามีแบนด์วิดท์สูงสุด 12 GB/s เทียบกับ 9 GB/s สำหรับ Graviton 3 เวลาแฝงหนึ่งเลนคือ 120 ns สำหรับเซิร์ฟเวอร์ Graviton 3 เทียบกับ 90 ns สำหรับโปรเซสเซอร์ Intel Graviton 3 ดูเหมือนจะรองรับการโหลดพร้อมกันประมาณ 19 ตัวต่อคอร์ เทียบกับประมาณ 25 สำหรับโปรเซสเซอร์ Intel

ดังนั้น Intel จึงเป็นผู้ชนะ แต่ Graviton 3 มีความเท่าเทียมกันในระดับหน่วยความจำที่ดี… ดีกว่าชิป Intel รุ่นเก่า (เช่น Skylake) และดีกว่าความพยายามครั้งแรกที่เซิร์ฟเวอร์ที่ใช้ ARM

รหัสที่มาสามารถใช้ได้ ฉันใช้ Ubuntu 22.04 และ GCC 11 เครื่องทั้งหมดมีขนาดหน้าเล็ก (4kB) ฉันเลือกที่จะไม่ปรับขนาดหน้าสำหรับการทดสอบเหล่านี้

ราคา Graviton 3 อยู่ที่ 2.32 เหรียญสหรัฐต่อชั่วโมง (64 vCPU) เทียบกับ 2.448 เหรียญสหรัฐต่อชั่วโมงสำหรับ Ice Lake ดังนั้น Graviton 3 จึงมีราคาถูกกว่าชิป Intel เล็กน้อย

เมื่อฉันเขียนโพสต์เหล่านี้ เปรียบเทียบผลิตภัณฑ์หนึ่งกับอีกผลิตภัณฑ์หนึ่ง มักมีจดหมายแสดงความเกลียดชังอยู่เสมอ ดังนั้นให้ฉันทื่อ ฉันรักชิปทั้งหมดเท่า ๆ กัน

หากคุณต้องการทราบว่าระบบใดดีที่สุดสำหรับแอปพลิเคชันของคุณ: เรียกใช้การวัดประสิทธิภาพ การวัดประสิทธิภาพที่ครอบคลุมพบว่าฮาร์ดแวร์ ARM ของ Amazon อาจเป็นประโยชน์สำหรับงานที่ใช้พื้นที่เก็บข้อมูลมาก

อ่านเพิ่มเติม : ฉันชอบ Graviton 3: First Impressions

ขนาดโครงสร้างข้อมูลและการเข้าถึงแคชไลน์

ภาพบุคคล2018facebook.jpg

ในหลายระบบ หน่วยความจำเข้าถึงได้ในบล็อกแบบตายตัวที่เรียกว่า “แคชไลน์” ในระบบ Intel สายแคชจะขยาย 64 ไบต์ นั่นคือถ้าคุณเข้าถึงหน่วยความจำที่ไบต์แอดเดรส 64, 65… สูงสุด 127… ทั้งหมดอยู่ในแคชบรรทัดเดียวกัน บรรทัดแคชถัดไปเริ่มต้นที่ที่อยู่ 128 เป็นต้น

ในทางกลับกัน ข้อมูลในซอฟต์แวร์มักจะถูกจัดระเบียบในโครงสร้างข้อมูลที่มีขนาดคงที่ (เป็นไบต์) เรามักจะจัดระเบียบโครงสร้างข้อมูลเหล่านี้ในอาร์เรย์ โดยทั่วไป โครงสร้างข้อมูลอาจอยู่บนแคชมากกว่าหนึ่งบรรทัด ตัวอย่างเช่น ถ้าฉันใส่โครงสร้างข้อมูล 5 ไบต์ที่ไบต์แอดเดรส 127 โครงสร้างนั้นจะครอบครองไบต์สุดท้ายของบรรทัดแคชหนึ่งบรรทัด และสี่ไบต์ในบรรทัดแคชถัดไป

เมื่อโหลดโครงสร้างข้อมูลจากหน่วยความจำ โมเดลที่ไร้เดียงสาของต้นทุนคือจำนวนบรรทัดแคชที่เข้าถึงได้ หากโครงสร้างข้อมูลของคุณมีขนาด 32 ไบต์หรือ 64 ไบต์ และคุณได้จัดแนวองค์ประกอบแรกของอาร์เรย์แล้ว คุณจะต้องเข้าถึงแคชบรรทัดเดียวทุกครั้งที่โหลดโครงสร้างข้อมูล

จะเกิดอะไรขึ้นหากโครงสร้างข้อมูลของฉันมี 5 ไบต์ สมมติว่าฉันรวมไว้ในอาร์เรย์โดยใช้เพียง 5 ไบต์ต่ออินสแตนซ์ จะเกิดอะไรขึ้นหากฉันเลือกหนึ่งรายการโดยสุ่ม… ฉันจะแตะแคชได้กี่บรรทัด ตามคาด โดยเฉลี่ยแล้ว คำตอบจะมีมากกว่า 1 แคชไลน์

ให้เราสรุป

สมมติว่าโครงสร้างข้อมูลของฉันครอบคลุม z ไบต์ ให้ g เป็นตัวหารร่วมมากระหว่าง z และ 64 สมมติว่าคุณโหลดโครงสร้างข้อมูลหนึ่งอินสแตนซ์จากอาร์เรย์ขนาดใหญ่แบบสุ่ม โดยทั่วไป จำนวนการเข้าถึงแคชเพิ่มเติมที่คาดไว้คือ (z – g)/64 จำนวนการเข้าถึงแคชไลน์ทั้งหมดที่คาดไว้คือ 1 + (z – g)/64 คุณสามารถตรวจสอบว่ามันใช้ได้สำหรับ z = 32 เนื่องจาก g คือ 32 และคุณมี (z – g)/64 คือ (32-32)/64 หรือศูนย์

ฉันสร้างตารางต่อไปนี้สำหรับโครงสร้างข้อมูลทั้งหมดที่มีขนาดไม่เกินแคชไลน์ สถานการณ์กรณีที่เลวร้ายที่สุดคือโครงสร้างข้อมูลที่มีขนาด 63 ไบต์: จากนั้นคุณเกือบจะแตะสองบรรทัดของแคชเกือบทุกครั้ง

ฉันพบว่ามันน่าสนใจที่คุณมีจำนวนแคชไลน์ที่คาดหวังไว้เท่ากันสำหรับโครงสร้างข้อมูลขนาด 17, 20, 24 ไม่เป็นไปตามนั้นว่าการคำนวณต้นทุนโครงสร้างข้อมูลขนาด 24 ไบต์จะเท่ากับต้นทุนของโครงสร้างข้อมูลที่ครอบคลุม 17 ไบต์ โครงสร้างข้อมูลที่มีขนาดเล็กกว่าควรใช้งานได้ดีกว่า เนื่องจากจะใส่ลงในแคชของ CPU ได้ง่ายกว่า

ขนาดของโครงสร้างข้อมูล (z) การเข้าถึงสายแคชที่คาดไว้
1 1.0
2 1.0
3 1.03125
4 1.0
5 1.0625
6 1.0625
7 1.09375
8 1.0
9 1.125
10 1.125
11 1.15625
12 1.125
13 1.1875
14 1.1875
15 1.21875
16 1.0
17 1.25
18 1.25
19 1.28125
20 1.25
21 1.3125
22 1.3125
23 1.34375
24 1.25
25 1.375
26 1.375
27 1.40625
28 1.375
29 1.4375
30 1.4375
31 1.46875
32 1.0
33 1.5
34 1.5
35 1.53125
36 1.5
37 1.5625
38 1.5625
39 1.59375
40 1.5
41 1.625
42 1.625
43 1.65625
44 1.625
45 1.6875
46 1.6875
47 1.71875
48 1.5
49 1.75
50 1.75
51 1.78125
52 1.75
53 1.8125
54 1.8125
55 1.84375
56 1.75
57 1.875
58 1.875
59 1.90625
60 1.875
61 1.9375
62 1.9375
63 1.96875
64 1.0