จะระบุตัวชี้เป็นที่เก็บข้อมูล thread_local ใน C ++ ได้อย่างไร

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

        114 [1]                         top_data_c[pc] += w1 * bottom_data_hwc[o1 + pc];
0x55555558bf70  <+ 2624>        c4 c1 78 10 0c 02                 vmovups (%r10,%rax,1),%xmm1
0x55555558bf76  <+ 2630>        48 83 c1 01                       add    $0x1,%rcx
0x55555558bf7a  <+ 2634>        c4 c3 75 18 4c 02 10 01           vinsertf128 $0x1,0x10(%r10,%rax,1),%ymm1,%ymm1
0x55555558bf82  <+ 2642>        c4 c2 25 a8 0c 04                 vfmadd213ps (%r12,%rax,1),%ymm11,%ymm1
0x55555558bf88  <+ 2648>        c4 c1 7c 29 0c 04                 vmovaps %ymm1,(%r12,%rax,1)
        115 [1]                         top_data_c[pc] += w2 * bottom_data_hwc[o2 + pc];
0x55555558bf8e  <+ 2654>        c4 c1 78 10 04 01                 vmovups (%r9,%rax,1),%xmm0
0x55555558bf94  <+ 2660>        c4 c3 7d 18 44 01 10 01           vinsertf128 $0x1,0x10(%r9,%rax,1),%ymm0,%ymm0
0x55555558bf9c  <+ 2668>        c4 c2 7d b8 ca                    vfmadd231ps %ymm10,%ymm0,%ymm1
0x55555558bfa1  <+ 2673>        c4 c1 7c 29 0c 04                 vmovaps %ymm1,(%r12,%rax,1)
        116 [1]                         top_data_c[pc] += w3 * bottom_data_hwc[o3 + pc];
0x55555558bfa7  <+ 2679>        c4 c1 78 10 04 00                 vmovups (%r8,%rax,1),%xmm0
0x55555558bfad  <+ 2685>        c4 c3 7d 18 44 00 10 01           vinsertf128 $0x1,0x10(%r8,%rax,1),%ymm0,%ymm0
0x55555558bfb5  <+ 2693>        c4 c2 75 98 c1                    vfmadd132ps %ymm9,%ymm1,%ymm0
0x55555558bfba  <+ 2698>        c4 c1 7c 29 04 04                 vmovaps %ymm0,(%r12,%rax,1)
        117 [1]                         top_data_c[pc] += w4 * bottom_data_hwc[o4 + pc];
0x55555558bfc0  <+ 2704>        c5 f8 10 0c 07                    vmovups (%rdi,%rax,1),%xmm1
0x55555558bfc5  <+ 2709>        c4 e3 75 18 4c 07 10 01           vinsertf128 $0x1,0x10(%rdi,%rax,1),%ymm1,%ymm1
0x55555558bfcd  <+ 2717>        c4 c2 75 b8 c0                    vfmadd231ps %ymm8,%ymm1,%ymm0
0x55555558bfd2  <+ 2722>        c4 c1 7c 29 04 04                 vmovaps %ymm0,(%r12,%rax,1)
0x55555558bfd8  <+ 2728>        48 83 c0 20                       add    $0x20,%rax
0x55555558bfdc  <+ 2732>        48 39 4d c0                       cmp    %rcx,-0x40(%rbp)
0x55555558bfe0  <+ 2736>        77 8e                             ja     0x55555558bf70

อย่างไรก็ตาม เมื่อฉันเปลี่ยนตัวชี้เป็นตัวแปร "stack array" ในเครื่อง เช่น T top_data_c[1024] คำสั่ง store จะปรากฏที่ส่วนท้ายของลูปเท่านั้น:

        114 [1]                         top_data_c[pc] += w1 * bottom_data_hwc[o1 + pc];
0x55555558bbe0  <+ 1712>        c5 f8 10 0c 03                    vmovups (%rbx,%rax,1),%xmm1
0x55555558bbe5  <+ 1717>        48 83 c1 01                       add    $0x1,%rcx
0x55555558bbe9  <+ 1721>        c4 e3 75 18 4c 03 10 01           vinsertf128 $0x1,0x10(%rbx,%rax,1),%ymm1,%ymm1
0x55555558bbf1  <+ 1729>        c4 c2 25 a8 0c 04                 vfmadd213ps (%r12,%rax,1),%ymm11,%ymm1
0x55555558bbf7  <+ 1735>        c5 fc 28 c1                       vmovaps %ymm1,%ymm0
        115 [1]                         top_data_c[pc] += w2 * bottom_data_hwc[o2 + pc];
0x55555558bbfb  <+ 1739>        c4 c1 78 10 0c 03                 vmovups (%r11,%rax,1),%xmm1
0x55555558bc01  <+ 1745>        c4 c3 75 18 4c 03 10 01           vinsertf128 $0x1,0x10(%r11,%rax,1),%ymm1,%ymm1
0x55555558bc09  <+ 1753>        c4 c2 7d 98 ca                    vfmadd132ps %ymm10,%ymm0,%ymm1
        116 [1]                         top_data_c[pc] += w3 * bottom_data_hwc[o3 + pc];
0x55555558bc0e  <+ 1758>        c4 c1 78 10 04 02                 vmovups (%r10,%rax,1),%xmm0
0x55555558bc14  <+ 1764>        c4 c3 7d 18 44 02 10 01           vinsertf128 $0x1,0x10(%r10,%rax,1),%ymm0,%ymm0
0x55555558bc1c  <+ 1772>        c4 e2 35 b8 c8                    vfmadd231ps %ymm0,%ymm9,%ymm1
        117 [1]                         top_data_c[pc] += w4 * bottom_data_hwc[o4 + pc];
0x55555558bc21  <+ 1777>        c4 c1 78 10 04 01                 vmovups (%r9,%rax,1),%xmm0
0x55555558bc27  <+ 1783>        c4 c3 7d 18 44 01 10 01           vinsertf128 $0x1,0x10(%r9,%rax,1),%ymm0,%ymm0
0x55555558bc2f  <+ 1791>        c4 c2 75 98 c0                    vfmadd132ps %ymm8,%ymm1,%ymm0
0x55555558bc34  <+ 1796>        c4 c1 7c 29 04 04                 vmovaps %ymm0,(%r12,%rax,1)
0x55555558bc3a  <+ 1802>        48 83 c0 20                       add    $0x20,%rax
0x55555558bc3e  <+ 1806>        48 3b 8d c8 fb ff ff              cmp    -0x438(%rbp),%rcx
0x55555558bc45  <+ 1813>        72 99                             jb     0x55555558bbe0

ปรากฏว่าคอมไพเลอร์เก็บการดำเนินการของตัวชี้ให้ห่างจากการปรับให้เหมาะสมเนื่องจากความไม่ปลอดภัยของเธรด

การประกาศอาร์เรย์สแต็กหรือการคัดลอกตัวแปรชั่วคราวจากและด้านหลังดูสกปรกมากในการใช้งานดังกล่าว มีวิธีใดบ้างที่จะทำให้เธรดหน่วยเก็บข้อมูลตัวชี้นั้นปลอดภัยต่อความรู้สึกของคอมไพเลอร์? แน่นอนว่าการคำนวณดังกล่าวปลอดภัยต่อเธรดโดยสิ้นเชิง (ทำงานค่อนข้างคล้ายกับ GPU)


person Midori Yakumo    schedule 14.03.2019    source แหล่งที่มา


คำตอบ (1)


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

ปัญหาที่แท้จริงที่นี่คือการใช้นามแฝง - คอมไพเลอร์ ไม่สามารถ ถือว่า top_data_c[pc] ไม่ได้อยู่ที่ไหนสักแห่งใน bottom_data_hwc (มันอาจจะเหมือนกับ bottom_data_hwc[o4 + pc] ใครจะรู้) ดังนั้นจึงต้องดำเนินการเก็บ สิ่งที่บรรเทาปัญหานี้ในกรณี "stack array" คือ (ถ้าฉันจำไม่ผิด) ความจริงที่ว่า top_data_c เป็นอาร์เรย์แทนที่จะเป็นตัวชี้ (ไม่ใช่ว่าอยู่บนสแต็กหรือเป็นเธรดในเครื่อง)

การใช้กฎนามแฝงที่เข้มงวดในระยะสั้น: คอมไพเลอร์สามารถสันนิษฐานได้ว่าตัวชี้ไปยังประเภทที่เข้ากันไม่ได้ (เช่น int และ double) ไม่สามารถชี้ไปยังตำแหน่งเดียวกันได้ หากคุณอยู่ใน void foo(int* x, double* y) ดังนั้น ภายใต้กฎนามแฝงที่เข้มงวด การเขียนถึง x ไม่สามารถเปลี่ยนสิ่งที่คุณอ่านจาก y ได้ (และในทางกลับกัน) ดังนั้นคอมไพลเลอร์จึงมีอิสระที่จะเรียงลำดับใหม่หรือเลื่อนร้านค้าและอ่านถึง/จาก x และ y ภายในฟังก์ชันนี้เป็น มันต้องการ

แต่ภายใน void foo(double* x, double* y) การรับประกันนี้จะหายไป หาก x == y (หรือ x == y + n) เขียนและอ่านไปยัง x และ y สามารถโต้ตอบซึ่งกันและกันได้ และคอมไพเลอร์ไม่มีทางเลือกอื่นนอกจากดำเนินการโหลด/จัดเก็บแต่ละรายการ

คุณควรตรวจสอบคำหลักที่เกี่ยวข้องกับ restrict ซึ่งส่งสัญญาณไปยังคอมไพลเลอร์ "การเข้าถึง x เกิดขึ้น เท่านั้น ถึง x (และสำเนา) ในฟังก์ชันนี้" วัสดุบางอย่าง:

คำหลักที่จำกัดหมายถึงอะไรในภาษา C++

https://cellPerformance.beyond3d.com/articles/2006/05/demystifying-the-restrict-keyword.html

http://assemblyrquired.crashworks.org/load-hit-stores-and-the-__restrict-keyword/

person Max Langhof    schedule 14.03.2019
comment
เย็น! ขอบคุณแม็กซ์! ฉันเพิ่ม restrict แล้วและมันก็เป็นไปตามที่ฉันคาดไว้! - person Midori Yakumo; 14.03.2019
comment
อีกคำถามหนึ่ง จำกัด ทำงานอย่างไรกับตัวชี้ const ทำไมจึงเป็น memcpy (void *__restrict __dest, const void *__restrict __src) แต่ไม่ใช่ memcpy (void *__restrict __dest, const void *src) ? - person Midori Yakumo; 15.03.2019
comment
const void* หมายความว่าคุณไม่สามารถเขียนไปยังตำแหน่งที่ชี้ได้ ดังนั้น restrict จะยกเว้นเฉพาะนามแฝงการอ่านเท่านั้น (เนื่องจากคุณไม่สามารถเขียนได้) นอกเหนือจากนั้น const ไม่มีการโต้ตอบกับ restrict... แต่ก็ไม่มีประโยชน์ใดเป็นพิเศษจากการจำกัด ทั้งสองอย่างกับการจำกัดอย่างใดอย่างหนึ่ง ตราบใดที่ทั้งสองนั้นเป็นเพียงพอยน์เตอร์เดียวที่ถูกยกเลิกการอ้างอิงภายในฟังก์ชัน: godbolt.org/z/VuSQFu. แต่การใส่ restrict บนอาร์กิวเมนต์ทั้งสองจะทำให้ผู้ใช้เข้าใจประเด็นได้ชัดเจน และอนุญาตให้นำไปประยุกต์ใช้เพื่อยกเลิกตัวแปรตัวชี้ร่วมโดยไม่ใช้นามแฝงซึ่งก่อให้เกิดปัญหาด้านประสิทธิภาพ - person Max Langhof; 15.03.2019