ทำไมรูปภาพของ Alpine Docker ถึงช้ากว่ารูปภาพ Ubuntu ถึง 50%


35

ฉันสังเกตเห็นว่าแอปพลิเคชั่น Python ของฉันช้ากว่ามากเมื่อรันบนpython:2-alpine3.6โดยไม่ใช้ Docker บน Ubuntu ฉันใช้คำสั่งเกณฑ์มาตรฐานขนาดเล็กสองคำสั่งและมีความแตกต่างอย่างมากระหว่างระบบปฏิบัติการสองระบบทั้งเมื่อฉันใช้งานบนเซิร์ฟเวอร์ Ubuntu และเมื่อฉันใช้ Docker สำหรับ Mac

$ BENCHMARK="import timeit; print(timeit.timeit('import json; json.dumps(list(range(10000)))', number=5000))"
$ docker run python:2-alpine3.6 python -c $BENCHMARK
7.6094589233
$ docker run python:2-slim python -c $BENCHMARK
4.3410820961
$ docker run python:3-alpine3.6 python -c $BENCHMARK
7.0276606959
$ docker run python:3-slim python -c $BENCHMARK
5.6621271420

ฉันลองใช้ 'มาตรฐาน' ต่อไปนี้ซึ่งไม่ได้ใช้ Python:

$ docker run -ti ubuntu bash
root@6b633e9197cc:/# time $(i=0; while (( i < 9999999 )); do (( i ++
)); done)

real    0m39.053s
user    0m39.050s
sys     0m0.000s
$ docker run -ti alpine sh
/ # apk add --no-cache bash > /dev/null
/ # bash
bash-4.3# time $(i=0; while (( i < 9999999 )); do (( i ++ )); done)

real    1m4.277s
user    1m4.290s
sys     0m0.000s

สิ่งที่อาจทำให้เกิดความแตกต่างนี้


1
@ ดูอีกครั้ง: เวลาเริ่มต้นหลังจากการติดตั้งทุบตีภายในเปลือกทุบเปิดตัว
Underyx

คำตอบ:


45

ฉันใช้มาตรฐานเดียวกันกับที่คุณเคยใช้เพียง Python 3:

$ docker run python:3-alpine3.6 python --version
Python 3.6.2
$ docker run python:3-slim python --version
Python 3.6.2

ทำให้เกิดความแตกต่างมากกว่า 2 วินาที:

$ docker run python:3-slim python -c "$BENCHMARK"
3.6475560404360294
$ docker run python:3-alpine3.6 python -c "$BENCHMARK"
5.834922112524509

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

นี่คือความแตกต่างระหว่างคำสั่ง strace ที่สูงกว่า เอาต์พุตเริ่มแตกต่างจากบรรทัด 269 แน่นอนว่ามีที่อยู่แตกต่างกันในหน่วยความจำ แต่อย่างอื่นมันคล้ายกันมาก เวลาส่วนใหญ่ใช้เวลาอย่างเห็นได้ชัดรอให้pythonคำสั่งเสร็จสิ้น

หลังจากติดตั้งstraceลงในคอนเทนเนอร์ทั้งสองเราสามารถรับการติดตามที่น่าสนใจมากขึ้น (ฉันได้ลดจำนวนการทำซ้ำในเบนช์มาร์กเป็น 10)

ตัวอย่างเช่น, glibcกำลังโหลดไลบรารีในลักษณะดังต่อไปนี้ (บรรทัดที่ 182):

openat(AT_FDCWD, "/usr/local/lib/python3.6", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
getdents(3, /* 205 entries */, 32768)   = 6824
getdents(3, /* 0 entries */, 32768)     = 0

รหัสเดียวกันใน musl :

open("/usr/local/lib/python3.6", O_RDONLY|O_DIRECTORY|O_CLOEXEC) = 3
fcntl(3, F_SETFD, FD_CLOEXEC)           = 0
getdents64(3, /* 62 entries */, 2048)   = 2040
getdents64(3, /* 61 entries */, 2048)   = 2024
getdents64(3, /* 60 entries */, 2048)   = 2032
getdents64(3, /* 22 entries */, 2048)   = 728
getdents64(3, /* 0 entries */, 2048)    = 0

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

  • glibc กับ 10 ซ้ำ:

    write(1, "0.032388824969530106\n", 210.032388824969530106)
    
  • musl กับ 10 ซ้ำ:

    write(1, "0.035214247182011604\n", 210.035214247182011604)
    

muslช้าลง 0.0028254222124814987 วินาที เมื่อความแตกต่างเพิ่มขึ้นตามจำนวนการทำซ้ำฉันจะถือว่าความแตกต่างอยู่ในการจัดสรรหน่วยความจำของวัตถุ JSON

หากเราลดมาตรฐานในการนำเข้า แต่เพียงผู้เดียวjsonเราสังเกตว่าความแตกต่างนั้นไม่ใหญ่มากนัก:

$ BENCHMARK="import timeit; print(timeit.timeit('import json;', number=5000))"
$ docker run python:3-slim python -c "$BENCHMARK"
0.03683806210756302
$ docker run python:3-alpine3.6 python -c "$BENCHMARK"
0.038280246779322624

การโหลดไลบรารี่ของหลามดูคล้ายคลึงกัน การสร้างlist()ความแตกต่างที่ยิ่งใหญ่กว่า:

$ BENCHMARK="import timeit; print(timeit.timeit('list(range(10000))', number=5000))"
$ docker run python:3-slim python -c "$BENCHMARK"
0.5666235145181417
$ docker run python:3-alpine3.6 python -c "$BENCHMARK"
0.6885563563555479

เห็นได้ชัดว่าการดำเนินการที่แพงที่สุดคือ json.dumps()ซึ่งอาจชี้ไปที่ความแตกต่างในการจัดสรรหน่วยความจำระหว่างไลบรารีเหล่านั้น

มองอีกครั้งที่มาตรฐาน , muslมันช้าลงเล็กน้อยในการจัดสรรหน่วยความจำ:

                          musl  | glibc
-----------------------+--------+--------+
Tiny allocation & free |  0.005 | 0.002  |
-----------------------+--------+--------+
Big allocation & free  |  0.027 | 0.016  |
-----------------------+--------+--------+

ฉันไม่แน่ใจว่า "การจัดสรรครั้งใหญ่" มีความหมายอย่างไร แต่muslช้าลงเกือบ 2 เท่าซึ่งอาจมีความสำคัญเมื่อคุณทำซ้ำการดำเนินการดังกล่าวเป็นพัน ๆ ครั้งหรือหลายล้านครั้ง


12
แก้ไขเพียงไม่กี่ musl ไม่ใช่การนำ glibc ของอัลไพน์มาใช้เอง 1st musl ไม่ใช่การใช้งาน glibc อีกต่อไป แต่การใช้libc ที่แตกต่างกันตามมาตรฐาน POSIX คิดถึงที่ 2 ไม่ได้เป็นอัลไพน์ของตัวเองสิ่งที่มันเป็นแบบสแตนด์อโลนโครงการที่ไม่เกี่ยวข้องและคิดถึงไม่ได้ใช้เพียงแค่ในอัลไพน์
Jakub Jirutka

เนื่องจาก musl libc ดูเหมือนจะเป็นมาตรฐานที่ดีกว่า * ไม่ต้องพูดถึงการใช้งานที่ใหม่กว่าทำไมมันจึงดูด้อยกว่า glibc ในกรณีเหล่านี้? * CF wiki.musl-libc.org/functional-differences-from-glibc.html
Forest

ความแตกต่างของ 0.0028 วินาทีมีนัยสำคัญทางสถิติหรือไม่ ค่าเบี่ยงเบนสัมพัทธ์มีค่าเพียง 0.0013% และคุณได้ตัวอย่าง 10 ตัวอย่าง ส่วนเบี่ยงเบนมาตรฐาน (โดยประมาณ) คืออะไรสำหรับการวิ่ง 10 ครั้ง (หรือแม้แต่ความแตกต่างสูงสุด - นาที)
Peter Mortensen

@PeterMortensen สำหรับคำถามเกี่ยวกับผลลัพธ์มาตรฐานคุณควรอ้างอิงรหัส Eta Labs: etalabs.net/libc-bench.htmlเช่นการทดสอบความเครียด malloc ซ้ำ 100k ครั้ง ผลลัพธ์อาจขึ้นอยู่กับรุ่นไลบรารี่รุ่น GCC และซีพียูที่ใช้เป็นหลัก
Tombart
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.