ไหนจะเร็วกว่าที่จะลบบรรทัดแรกในไฟล์ ... เซดหรือท้าย


14

ในคำตอบนี้ ( ฉันจะลบบรรทัดแรกของไฟล์ที่มี sed ได้อย่างไร ) มีสองวิธีในการลบระเบียนแรกในไฟล์:

sed '1d' $file >> headerless.txt

** ---------------- หรือ ----------------**

tail -n +2 $file >> headerless.txt

โดยส่วนตัวแล้วฉันคิดว่าtailตัวเลือกนั้นเป็นที่ชื่นชอบและน่าอ่านมากกว่า แต่อาจเป็นเพราะฉันถูกท้าทาย

วิธีใดเร็วที่สุด


5
ไม่ใช่คำตอบ แต่ข้อควรพิจารณาที่เป็นไปได้ก็sedคือพกพาได้มากกว่า: "+2" สำหรับใช้tailงานได้ดีบน Ubuntu ซึ่งใช้ GNU tailแต่ไม่สามารถใช้กับ BSD tailได้
John N

@ JohnN ขอบคุณสำหรับการแบ่งปันtailความเข้ากันไม่ได้ข้ามแพลตฟอร์ม
WinEunuuchs2Unix

3
@John N "+2" สำหรับ tail ใช้งานได้ดีบน Mac ที่ Sierra ใช้งานได้ซึ่งอ้างว่าใช้คำสั่งหาง BSD
Nick Sillito

ใช่คุณค่อนข้างถูกต้อง - ฉันเพิ่งเรียกใช้อีกครั้งและคราวนี้ตรวจสอบอินพุต ซึ่งฉันควรจะทำในครั้งแรก มันเป็น POSIX ด้วย / slinks off, embarrased
John N

2
@JohnN คุณไม่ผิดอย่างสมบูรณ์ ในอดีตที่ผ่านมา, UNIX ไม่ให้ตัวเลือกและใช้ไวยากรณ์-n tail +2 $fileดูfreebsd.org/cgi/…เป็นไปได้ว่าคุณกำลังคิดถึงสิ่งนั้นแทนที่จะเป็นหนึ่งใน BSD สมัยใหม่
hvd

คำตอบ:


28

ประสิทธิภาพของsedvs. tailเพื่อลบบรรทัดแรกของไฟล์

TL; DR

  • sed มีประสิทธิภาพและเอนกประสงค์ แต่นี่คือสิ่งที่ทำให้ช้าโดยเฉพาะอย่างยิ่งสำหรับไฟล์ขนาดใหญ่ที่มีหลายบรรทัด

  • tail ทำสิ่งที่เรียบง่ายเพียงอย่างเดียว แต่สิ่งนั้นทำได้ดีและรวดเร็วแม้แต่ไฟล์ที่ใหญ่กว่าที่มีหลายบรรทัด

สำหรับไฟล์ขนาดเล็กและขนาดกลางsedและtailทำงานอย่างรวดเร็วเช่นเดียวกัน (หรือช้าขึ้นอยู่กับความคาดหวังของคุณ) อย่างไรก็ตามสำหรับไฟล์อินพุตขนาดใหญ่ (หลาย MB) ความแตกต่างของประสิทธิภาพจะเพิ่มขึ้นอย่างมาก (ลำดับความสำคัญของไฟล์ในช่วงหลายร้อย MB) ด้วยtailsedประสิทธิภาพสูงกว่าอย่างเห็นได้ชัด

การทดลอง

การเตรียมการทั่วไป:

คำสั่งของเราในการวิเคราะห์คือ:

sed '1d' testfile > /dev/null
tail -n +2 testfile > /dev/null

โปรดทราบว่าฉันกำลังส่งออกไปยังท่อ /dev/nullแต่ละครั้งเพื่อกำจัดเอาต์พุตเทอร์มินัลหรือการเขียนไฟล์เป็นคอขวดของประสิทธิภาพ

มาตั้งค่า RAM disk เพื่อกำจัด I / O disk ที่อาจเป็นปัญหาคอขวด ฉันมีการtmpfsติดตั้งส่วนตัวที่/tmpดังนั้นฉันก็แค่วางไว้ที่testfileนั่นสำหรับการทดลองนี้

จากนั้นฉันก็สร้างไฟล์ทดสอบแบบสุ่มที่มีจำนวนบรรทัดที่ระบุ$numoflinesด้วยความยาวบรรทัดแบบสุ่มและข้อมูลแบบสุ่มโดยใช้คำสั่งนี้ (โปรดทราบว่ามันไม่เหมาะสมอย่างแน่นอนมันช้ามาก ๆ ประมาณ> 2M บรรทัด แต่ใครสนใจมันไม่ใช่ สิ่งที่เรากำลังวิเคราะห์):

cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n "$numoflines" > testfile

โอ้ววววว แล็ปท็อปทดสอบของฉันใช้งาน Ubuntu 16.04, 64 บิตบน Intel i5-6200U CPU เพียงเพื่อการเปรียบเทียบ

กำหนดเวลาไฟล์ขนาดใหญ่:

การตั้งค่าขนาดใหญ่testfile:

การรันคำสั่งด้านบนด้วยการnumoflines=10000000สร้างไฟล์สุ่มที่มีเส้น 10M ครอบครองบิตมากกว่า 600 MB - มันค่อนข้างใหญ่ แต่เรามาเริ่มด้วยเพราะเราสามารถ:

$ wc -l testfile 
10000000 testfile

$ du -h testfile 
611M    testfile

$ head -n 3 testfile 
qOWrzWppWJxx0e59o2uuvkrfjQbzos8Z0RWcCQPMGFPueRKqoy1mpgjHcSgtsRXLrZ8S4CU8w6O6pxkKa3JbJD7QNyiHb4o95TSKkdTBYs8uUOCRKPu6BbvG
NklpTCRzUgZK
O/lcQwmJXl1CGr5vQAbpM7TRNkx6XusYrO

แสดงการเรียกใช้ตามกำหนดเวลาด้วยขนาดใหญ่ของเราtestfile:

ทีนี้ลองทำตามเวลาที่กำหนดด้วยคำสั่งทั้งคู่ก่อนเพื่อประเมินว่าเราทำงานขนาดใด

$ time sed '1d' testfile > /dev/null
real    0m2.104s
user    0m1.944s
sys     0m0.156s

$ time tail -n +2 testfile > /dev/null
real    0m0.181s
user    0m0.044s
sys     0m0.132s

เราได้เห็นผลชัดเจนจริงๆสำหรับไฟล์ขนาดใหญ่เป็นขนาดได้เร็วกว่าtail sedแต่เพื่อความสนุกและเพื่อให้แน่ใจว่าไม่มีผลข้างเคียงแบบสุ่มที่สร้างความแตกต่างอย่างมากลองทำ 100 ครั้ง:

$ time for i in {1..100}; do sed '1d' testfile > /dev/null; done
real    3m36.756s
user    3m19.756s
sys     0m15.792s

$ time for i in {1..100}; do tail -n +2 testfile > /dev/null; done
real    0m14.573s
user    0m1.876s
sys     0m12.420s

ข้อสรุปยังคงเหมือนเดิมsedไม่มีประสิทธิภาพในการลบบรรทัดแรกของไฟล์ขนาดใหญ่tailควรใช้ที่นั่น

และใช่ฉันรู้ว่าการสร้างลูปของ Bash ช้า แต่เราทำซ้ำได้ไม่กี่ครั้งที่นี่และเวลาที่ใช้ลูปธรรมดาไม่สำคัญเมื่อเทียบกับsed/tail runtimes

ไฟล์ขนาดเล็กเวลา:

การตั้งค่าขนาดเล็กtestfile:

ตอนนี้เพื่อความสมบูรณ์ลองดูกรณีทั่วไปที่คุณมีไฟล์อินพุตขนาดเล็กในช่วง kB มาสร้างไฟล์อินพุตแบบสุ่มด้วยnumoflines=100:

$ wc -l testfile 
100 testfile

$ du -h testfile 
8,0K    testfile

$ head -n 3 testfile 
tYMWxhi7GqV0DjWd
pemd0y3NgfBK4G4ho/
aItY/8crld2tZvsU5ly

ดำเนินการหมดเวลากับเราขนาดเล็ก testfile :

เนื่องจากเราคาดว่าการกำหนดเวลาสำหรับไฟล์ขนาดเล็กดังกล่าวจะอยู่ในช่วงไม่กี่มิลลิวินาทีจากประสบการณ์เราจะทำ 1,000 การทำซ้ำทันที:

$ time for i in {1..1000}; do sed '1d' testfile > /dev/null; done
real    0m7.811s
user    0m0.412s
sys     0m7.020s

$ time for i in {1..1000}; do tail -n +2 testfile > /dev/null; done
real    0m7.485s
user    0m0.292s
sys     0m6.020s

อย่างที่คุณเห็นการจับเวลานั้นค่อนข้างคล้ายกันไม่มีอะไรมากที่จะตีความหรือสงสัย สำหรับไฟล์ขนาดเล็กเครื่องมือทั้งสองนั้นเหมาะสมอย่างเท่าเทียมกัน


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

นี่คือระบบอะไร สำหรับ mac ของฉัน (เช่นอุปกรณ์ BSD) การทดสอบ / usr / share / dict / Words ให้ฉัน 0.09s สำหรับ sed และ 0.19s สำหรับ tail (และawk 'NR > 1'น่าสนใจ)
Kevin

5

นี่เป็นอีกทางเลือกหนึ่งโดยใช้เพียง bash builtins และcat:

{ read ; cat > headerless.txt; } < $file

$fileถูกเปลี่ยนเส้นทางไปยังการ{ }จัดกลุ่มคำสั่ง readเพียงแค่อ่านและทิ้งบรรทัดแรก ส่วนที่เหลือของกระแสข้อมูลจะถูกส่งไปcatยังไฟล์ที่จะเขียนไปยังไฟล์ปลายทาง

ใน Ubuntu 16.04 ของฉันประสิทธิภาพของสิ่งนี้และการtailแก้ปัญหาคล้ายกันมาก ฉันสร้างไฟล์ทดสอบที่ใหญ่โตด้วยseq:

$ seq 100000000 > 100M.txt
$ ls -l 100M.txt 
-rw-rw-r-- 1 ubuntu ubuntu 888888898 Dec 20 17:04 100M.txt
$

tail วิธีการแก้:

$ time tail -n +2 100M.txt > headerless.txt

real    0m1.469s
user    0m0.052s
sys 0m0.784s
$ 

cat/ รั้งโซลูชั่น:

$ time { read ; cat > headerless.txt; } < 100M.txt 

real    0m1.877s
user    0m0.000s
sys 0m0.736s
$ 

ตอนนี้ฉันมี Ubuntu VM สะดวกแล้วและเห็นการเปลี่ยนแปลงที่สำคัญในการกำหนดเวลาของทั้งคู่แม้ว่าพวกเขาทั้งหมดจะอยู่ใน ballpark เดียวกัน


1
+1 สำหรับคำตอบขอบคุณ นั่นเป็นทางออกที่น่าสนใจมากและฉันชอบวงเล็บปีกกาและขวาไปซ้ายอ่านผ่านลำดับชั้นของ bash (ไม่แน่ใจว่าฉันพูดถูกหรือเปล่า) เป็นไปได้หรือไม่ที่จะอัพเดตคำตอบของคุณด้วยขนาดของไฟล์อินพุตและผลลัพธ์การวัดระยะเวลาหากทำได้ง่ายพอ?
WinEunuuchs2Unix

@ WinEunuuchs2Unix Timings ที่เพิ่มเข้ามาถึงแม้ว่ามันจะไม่น่าเชื่อถือมากนักเนื่องจากเป็นบน VM ฉันไม่มีการติดตั้ง Ubuntu แบบโลหะเปลือยตอนนี้
Digital Trauma

ฉันไม่คิดว่า VM vs Bare Metal สำคัญเมื่อคุณเปรียบเทียบ VM กับ VM อย่างไรก็ตาม ขอบคุณสำหรับการพิสูจน์เวลา ฉันอาจจะไปด้วยtailแต่ก็ยังคิดว่าreadตัวเลือกนั้นยอดเยี่ยมมาก
WinEunuuchs2Unix

4

ลองใช้ในระบบของฉันและนำหน้าแต่ละคำสั่งด้วยtimeฉันได้ผลลัพธ์ต่อไปนี้:

sed:

real    0m0.129s
user    0m0.012s
sys     0m0.000s

และหาง:

real    0m0.003s
user    0m0.000s
sys     0m0.000s

ซึ่งแนะนำว่าในระบบของฉันอย่างน้อย AMD FX 8250 ที่ใช้ Ubuntu 16.04 ส่วนท้ายนั้นเร็วกว่ามาก ไฟล์ทดสอบมี 10,000 บรรทัดขนาด 540k ไฟล์ถูกอ่านจาก HDD


+1 สำหรับการตอบขอบคุณ ในการทดสอบแยกต่างหากใน AU Chatroom ผู้ใช้หนึ่งรายแสดงหางเป็น 10 เท่า (2.31 วินาที) เร็วกว่า sed (21.86 วินาที) โดยใช้ RAMDisk พร้อมไฟล์ 61 MB ฉันแก้ไขคำตอบของคุณเพื่อใช้บล็อคโค้ด แต่คุณอาจต้องการแก้ไขด้วยขนาดไฟล์ที่คุณใช้
WinEunuuchs2Unix

@Serg อย่างยุติธรรมว่านี้เป็นเพียงคำตอบที่พอสมควรและอาจคุณจะได้รับผลลัพธ์ที่แตกต่างกับการกำหนดค่าฮาร์ดแวร์ที่แตกต่างกัน, การทดสอบที่แตกต่างกันไฟล์ ฯลฯ
นิค Sillito

2
ไฟล์ที่ไม่ได้อยู่ในแคชเมื่อใช้sedอาจเล่นเป็นปัจจัยในผลลัพธ์นี้นั่นคือคำสั่งที่คุณได้ทดสอบพวกเขามา
Minix

ระบบประเภทใด ตามที่ฉันแสดงความคิดเห็นในโพสต์อื่นที่นี่บน mac ของฉันsedเร็วประมาณสองเท่า
Kevin

1

ไม่มีวิธีที่จะพูดซึ่งดีกว่าเพราะsedและtailไม่ใช่สิ่งเดียวที่ทำงานบนระบบระหว่างการทำงานของโปรแกรม ปัจจัยหลายอย่างเช่นดิสก์ i / o, เครือข่าย i / o, ซีพียูขัดจังหวะสำหรับกระบวนการที่มีลำดับความสำคัญสูงกว่า - สิ่งเหล่านี้มีผลต่อความเร็วในการรันโปรแกรมของคุณ

ทั้งคู่เขียนเป็นภาษา C ดังนั้นนี่ไม่ใช่ปัญหาทางภาษา แต่เป็นเรื่องเกี่ยวกับสิ่งแวดล้อมมากกว่า ตัวอย่างเช่นฉันมี SSD และในระบบของฉันนี้จะใช้เวลาเป็นไมโครวินาที แต่สำหรับไฟล์เดียวกันบนฮาร์ดไดรฟ์จะใช้เวลามากกว่าเพราะ HDD ช้าลงอย่างมาก ดังนั้นฮาร์ดแวร์ก็มีบทบาทสำคัญในเรื่องนี้เช่นกัน

มีบางสิ่งที่คุณอาจต้องการทราบเมื่อพิจารณาถึงคำสั่งให้เลือก:

  • จุดประสงค์ของคุณคืออะไร sedเป็นตัวแก้ไขกระแสสำหรับการแปลงข้อความ tailใช้สำหรับแสดงบรรทัดข้อความที่ต้องการ tailหากคุณต้องการที่จะจัดการกับเส้นและเพียงพิมพ์ออกใช้ sedหากคุณต้องการที่จะแก้ไขข้อความการใช้
  • tailมีไวยากรณ์ที่ง่ายกว่าsedดังนั้นใช้สิ่งที่คุณสามารถอ่านด้วยตัวคุณเองและสิ่งที่ผู้อื่นสามารถอ่านได้

อีกปัจจัยสำคัญคือปริมาณข้อมูลที่คุณกำลังประมวลผล ไฟล์ขนาดเล็กจะไม่ให้ประสิทธิภาพที่แตกต่าง ภาพน่าสนใจเมื่อคุณจัดการกับไฟล์ขนาดใหญ่ ด้วย BIGFILE.txt 2 GB เราจะเห็นว่าsedมีการเรียกใช้ระบบมากกว่าtailและจะทำงานช้าลงอย่างมาก

bash-4.3$ du -sh BIGFILE.txt 
2.0G    BIGFILE.txt
bash-4.3$ strace -c  sed '1d' ./BIGFILE.txt  > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 59.38    0.079781           0    517051           read
 40.62    0.054570           0    517042           write
  0.00    0.000000           0        10         1 open
  0.00    0.000000           0        11           close
  0.00    0.000000           0        10           fstat
  0.00    0.000000           0        19           mmap
  0.00    0.000000           0        12           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         2           rt_sigaction
  0.00    0.000000           0         1           rt_sigprocmask
  0.00    0.000000           0         1         1 ioctl
  0.00    0.000000           0         7         7 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0         2         2 statfs
  0.00    0.000000           0         1           arch_prctl
  0.00    0.000000           0         1           set_tid_address
  0.00    0.000000           0         1           set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00    0.134351               1034177        11 total
bash-4.3$ strace -c  tail  -n +2 ./BIGFILE.txt  > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 62.30    0.148821           0    517042           write
 37.70    0.090044           0    258525           read
  0.00    0.000000           0         9         3 open
  0.00    0.000000           0         8           close
  0.00    0.000000           0         7           fstat
  0.00    0.000000           0        10           mmap
  0.00    0.000000           0         4           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         1         1 ioctl
  0.00    0.000000           0         3         3 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00    0.238865                775615         7 total

+1 สำหรับการตอบขอบคุณ แต่ฉันไม่แน่ใจ
ว่าความ

@ WinEunuuchs2Unix ทีนี้คุณถามว่าคำสั่งไหนดีกว่าดังนั้นฉันตอบคำถามนั้นอย่างแน่นอน คำสั่งที่จะเลือกขึ้นอยู่กับคุณ หากคุณสามารถอ่านได้tailดีกว่าsed- ใช้สิ่งนั้น ฉันเองจะใช้pythonหรือawkมากกว่าsedเพราะมันจะซับซ้อน นอกจากนี้หากคุณกังวลเกี่ยวกับประสิทธิภาพลองมาพบกับความจริง - คุณจะเห็นผลลัพธ์เป็นหน่วยไมโครวินาทีที่นี่ คุณจะไม่รู้สึกแตกต่างเว้นแต่จะเป็นไฟล์ขนาดใหญ่ freakin ในช่วงของกิกะไบต์ที่คุณกำลังพยายามที่จะอ่าน
Sergiy Kolodyazhnyy

โอ้ฉันจะชื่นชมawkคำตอบเกินไป:) ... คำถามของฉันอยู่บนพื้นฐานของอีก AU Q & A (ในการเชื่อมโยง) awkและพวกเขาไม่เคยพูดถึง ฉันยอมรับความแตกต่างของเวลาเล็กน้อยในไฟล์ขนาดเล็ก ฉันแค่พยายามพัฒนานิสัยที่ดี
WinEunuuchs2Unix

1
@ WinEunuuchs2Unix awk 'NR!=1' input_file.txt แน่นอนว่านี่มันคือ มันทำให้ฉันมีผลเหมือนกันเท่าเทียมกันประมาณ 150 มิลลิวินาทีจำนวนเดียวกันทั้งและtail sedแต่ agian ฉันใช้ SSD ดังนั้นฉันจะบอกว่ามันเป็นฮาร์ดไดรฟ์และ CPU ที่สำคัญไม่ใช่คำสั่ง
Sergiy Kolodyazhnyy

1
@Serg แม้จะมีไฟล์ 60 MB ที่มี 1M บรรทัดเพียง 1000 รันก็sedใช้เวลานานกว่า 3 นาทีในขณะที่tailต้องการเพียง 20 วินาทีเท่านั้น นั่นคือไม่ว่าเลยจริงแน่นอนไม่ได้อยู่ในช่วงที่ใหญ่ GB
ผู้บัญชาการ Byte

1

คำตอบอันดับแรกไม่ได้คำนึงถึงการใช้ดิสก์ > /dev/null

หากคุณมีไฟล์ขนาดใหญ่และไม่ต้องการสร้างไฟล์ซ้ำชั่วคราวในดิสก์ของคุณลอง vim -c

$ cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n 10000000 > testfile
$ time sed -i '1d' testfile

real    0m59.053s
user    0m9.625s
sys     0m48.952s

$ cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n 10000000 > testfile
$ time vim -e -s testfile -c ':1d' -c ':wq'

real    0m8.259s
user    0m3.640s
sys     0m3.093s

แก้ไข: หากไฟล์มีขนาดใหญ่กว่าหน่วยความจำที่มีอยู่vim -cไม่ทำงานดูเหมือนว่ามันไม่ฉลาดพอที่จะทำการโหลดไฟล์แบบเพิ่มหน่วย


0

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.