เบื้องหลังของ Bun Install
(bun.com)- การติดตั้งแพ็กเกจของ Bun ทำงานได้เร็วมากเมื่อเทียบกับแพ็กเกจแมเนเจอร์แบบเดิม
- หัวใจสำคัญของความเร็วคือ แนวทางแบบระบบโปรแกรมมิง และ การลดจำนวน system call ให้เหลือน้อยที่สุด
- เพิ่มประสิทธิภาพผ่านกลยุทธ์เชิงลึก เช่น การเขียนโค้ดเนทีฟด้วยภาษา Zig, การใช้ไบนารีแคช, และการปรับแต่งให้เหมาะกับแต่ละ OS
- แม้แต่ในขั้นตอน แตกไฟล์ tarball และคัดลอกไฟล์ ก็มีการนำวิธีประสิทธิภาพสูงที่อาศัยคุณลักษณะของฮาร์ดแวร์มาใช้
- ปรับปรุง โครงสร้างข้อมูล อย่าง dependency graph และ lockfile เพื่อเพิ่มประสิทธิภาพของ CPU cache และการเข้าถึงหน่วยความจำ
ทำไม Bun Install ถึงเร็ว
bun installของ Bun ให้ประสิทธิภาพการติดตั้งแพ็กเกจโดยเฉลี่ย เร็วกว่า npm 7 เท่า, pnpm 4 เท่า, และ yarn 17 เท่า- นี่ไม่ใช่แค่ผลจากเบนช์มาร์ก แต่เกิดจากการ มองปัญหาการติดตั้งแพ็กเกจในมุมของระบบโปรแกรมมิงแทนที่จะเป็น JavaScript
- มีการปรับแต่งประสิทธิภาพอย่างจริงจังในหลายชั้น ทั้งการลด system call, การแคช manifest แบบไบนารี, การปรับแต่งการแตก tarball, และการคัดลอกไฟล์แบบเนทีฟของแต่ละ OS
ข้อจำกัดของสถาปัตยกรรม Node.js และแพ็กเกจแมเนเจอร์
- หลังการเปิดตัว Node.js ในปี 2009 โมเดล asynchronous IO ที่อิง event loop และ thread pool ก็ถูกนำมาใช้กับแพ็กเกจแมเนเจอร์ด้วย
- ในเวลานั้น ข้อจำกัดของฮาร์ดแวร์ เช่น ดิสก์ช้าและเครือข่ายช้า ทำให้แนวทาง asynchronous IO และการเรียก system call ถี่ ๆ เป็นทางเลือกที่สมเหตุสมผล
- แต่ในระบบสมัยใหม่ NVMe SSD, เครือข่ายความเร็วสูง, และ CPU ประสิทธิภาพสูง กลายเป็นเรื่องปกติ และคอขวดที่แท้จริงไม่ใช่ IO แต่คือ overhead ของ system call
ต้นทุนของ system call และ mode switch
- เมื่อโปรแกรมร้องขอการทำงานอย่างการอ่านไฟล์ จะต้อง สลับจาก user mode ไปเป็น kernel mode และกระบวนการนี้กิน CPU cycle ราคาแพงมาก (1000~1500 cycles)
- การติดตั้งแพ็กเกจโดยพื้นฐานต้องใช้ system call ตั้งแต่หลักหมื่นถึงหลักแสนครั้งขึ้นไป ทำให้แค่ต้นทุนในการสลับงานก็ใช้เวลา CPU ไปหลายวินาทีแล้ว
- ตัวอย่างเช่น เมื่อติดตั้ง React และ dependency ของมัน npm ใช้ system call ราว 1 ล้านครั้ง, yarn 4 ล้านครั้ง, pnpm 5 แสนครั้ง, ส่วน bun ใช้ 1.6 แสนครั้ง
ความต่างของแนวทางระหว่างแพ็กเกจแมเนเจอร์เดิมกับ Bun
- npm, pnpm, yarn ล้วน สร้างอยู่บน Node.js จึงต้องรัน JavaScript ผ่านหลายชั้นของ abstraction เช่น libuv, event loop, thread pool และตัวกลางสำหรับ system call
- ระหว่างทางมีต้นทุนสะสมจากการแปลงอาร์กิวเมนต์, คิวของ worker pool, การกระจายงานใน event loop, และ system call อย่าง futex (การซิงก์ล็อก)
- ผลลัพธ์คือ แพ็กเกจแมเนเจอร์ที่สร้างด้วย Node.js มีข้อจำกัดเชิงโครงสร้าง ทำให้ยากจะรีดประสิทธิภาพให้ใกล้เคียงเนทีฟจริง ๆ
Bun: เอนจินติดตั้งแบบเนทีฟที่เขียนด้วย Zig
- Bun เรียกใช้ system call โดยตรงด้วยภาษา Zig จึงข้ามทั้ง JavaScript engine และ abstraction layer ทั้งหมด
- ตัวอย่างเช่น การอ่านไฟล์ในโค้ด Zig สามารถเรียก
openat()ได้โดยตรงและรับข้อมูลกลับมาได้ทันที - เพราะฉะนั้น กระบวนการอ่านไฟล์นับหมื่นไฟล์จึงทำงานได้เร็วมากโดยไม่ต้องผ่าน thread pool, event loop หรือขั้นตอนแปลงข้อมูลเพิ่มเติม
- จากเบนช์มาร์ก Bun สามารถอ่าน
package.jsonได้ 146,057 ไฟล์ต่อวินาที ขณะที่ Node.js ช้ากว่ามาก โดยอยู่แถว 60,000 ไฟล์ต่อวินาที
การจัดการ dependency และการปรับแต่ง DNS
- เมื่อรัน
bun installBun จะวิเคราะห์ dependency ไปพร้อมกับทริกเกอร์ DNS prefetch แบบ asynchronous - ตัวอย่างเช่น บน macOS จะใช้ async DNS API ที่ไม่เป็นทางการของ Apple (
getaddrinfo_async_start()) เพื่อรองรับ การทำงานเครือข่ายพร้อมกันโดยไม่บล็อกเธรด - ส่วนแพ็กเกจแมเนเจอร์แบบเดิมอาศัย thread pool ของ libuv ซึ่งภายในยังรันโค้ดแบบบล็อกกิงจริงอยู่ จึงสิ้นเปลืองทรัพยากรโดยไม่จำเป็น
การแคช manifest ของแพ็กเกจแบบไบนารี
- npm และตัวอื่น ๆ แคช manifest เป็น JSON แต่ Bun จะ พาร์สหนึ่งครั้งแล้วแปลงผลลัพธ์นั้นเป็นไบนารี (
.npm) เพื่อเก็บไว้ - วิธีนี้ช่วย ลดความซ้ำซ้อนของสตริงและ overhead จากการพาร์ส และในหน่วยความจำจริงก็สามารถเข้าถึงค่าต่าง ๆ ได้ทันทีด้วยการคำนวณ offset โดยไม่ต้องสร้างอ็อบเจ็กต์ใหม่, พาร์สใหม่ หรือทำ garbage collection
- ใช้ header อย่าง ETag และ If-None-Match เพื่อตรวจเฉพาะการเปลี่ยนแปลง ทำให้ตรวจสอบความเป็นปัจจุบันได้โดยไม่ต้องพาร์สข้อมูลที่ไม่จำเป็น
- จากเบนช์มาร์ก การติดตั้งจากแคชของ Bun ยังเร็วกว่า npm แบบ fresh install เสียอีก
ประสิทธิภาพในการจัดการ Tarball (ไฟล์บีบอัด)
- แพ็กเกจแมเนเจอร์ทั่วไปจะรับ tarball แบบสตรีม ทำให้เมื่อบัฟเฟอร์หน่วยความจำไม่พอ จะเกิดการจัดสรรใหม่ คัดลอก และปรับขนาดซ้ำ ๆ อย่างต่อเนื่อง
- Bun จะ รับ tarball มาทั้งก้อนก่อนแล้วค่อยแตกไฟล์ และใช้ 4 ไบต์สุดท้ายของ gzip เพื่อทราบขนาดหลังคลายบีบอัดล่วงหน้า ทำให้ จองหน่วยความจำเพียงครั้งเดียว
- ยังใช้
libdeflateและเทคนิคอื่นเพื่อคลายบีบอัดได้เร็ว พร้อมตัดการคัดลอกซ้ำและการปรับขนาดที่ไม่จำเป็นออกทั้งหมด
การปรับแต่ง dependency graph และโครงสร้างข้อมูล
- แพ็กเกจแมเนเจอร์เดิมสร้างต้นไม้ dependency ด้วย JavaScript object และ pointer ทำให้หน่วยความจำกระจายตัวแบบสุ่มและเกิด CPU cache miss บ่อย (ปัญหา pointer chasing)
- Bun ใช้แพตเทิร์น Structure of Arrays (SoA) โดยเก็บแพ็กเกจทั้งหมด สตริงทั้งหมด และ dependency ทั้งหมดไว้ในบล็อกหน่วยความจำต่อเนื่องขนาดใหญ่
- การเข้าถึงแบบ offset/length ทำให้ CPU สามารถอ่านหลายแพ็กเกจพร้อมกันเป็นหน่วย cache line ได้ง่ายขึ้น (โครงสร้างที่เป็นมิตรกับแคช)
- lockfile ก็ถูกจัดเก็บให้สอดคล้องกับแพตเทิร์น SoA แทน JSON/YAML เพื่อให้ตัดสตริงซ้ำและเข้าถึงหน่วยความจำแบบลำดับได้ง่าย
- เคยมีการนำ lockfile แบบไบนารี (
bun.lockb) มาใช้เชิงทดลองด้วย แต่ภายหลังเปลี่ยนกลับเป็นฟอร์แมตข้อความธรรมดาที่อ่านง่ายกว่า เพราะการทำงานร่วมกันผ่าน Git แย่ลง
การปรับแต่งการคัดลอกไฟล์ตามแต่ละ OS
macOS
- ใช้ clonefile: โคลนทั้งไดเรกทอรีด้วยวิธี Copy-On-Write ผ่าน system call เพียงครั้งเดียว
- ช่วยลดการใช้พื้นที่ดิสก์ซ้ำซ้อนและเพิ่มความเร็วในการติดตั้งสูงสุด
- หาก
clonefileล้มเหลว จะ fallback เป็น per-directory cloning แล้วค่อย fallback ต่อไปที่copyfile
Linux
- ลองใช้ hard link ก่อน: สร้างเพียง reference ใหม่ให้ไฟล์เดิมโดยไม่ต้องสร้างไฟล์ใหม่จริง ๆ (ไม่มีการย้ายข้อมูลบนดิสก์)
- หากใช้ hard link ไม่ได้ บน Btrfs/XFS จะใช้
ioctl_ficloneเพื่อทำ Copy-On-Write - จากนั้นจึง fallback ไปที่
copy_file_range,sendfileและสุดท้ายเป็นการคัดลอกแบบcopyfileทั่วไป
สรุป
- Bun ก้าวข้ามข้อจำกัดด้านประสิทธิภาพแบบดั้งเดิมของแพ็กเกจแมเนเจอร์ ผ่านการลด system call, การใช้โครงสร้างไบนารี, การปรับแต่งตาม OS และการปรับปรุงโครงสร้างข้อมูล
- ส่งผลให้ไม่เพียงติดตั้งได้เร็วมาก แต่ยังมีประสิทธิภาพที่ดีขึ้นทั้งด้านหน่วยความจำและ CPU
- สามารถนำไปใช้กับโปรเจกต์ได้โดยยังรักษาความเข้ากันได้ไว้ โดยไม่จำเป็นต้องเปลี่ยนรันไทม์แยกต่างหากเมื่อเทียบกับแมเนเจอร์ที่อิง Node.js เดิม
- มอบประสบการณ์ที่ย่นเวลาติดตั้งจากระดับหลายนาทีในโค้ดเบสขนาดใหญ่ ให้เหลือเพียงหลักมิลลิวินาทีถึงไม่กี่วินาที
- นี่เป็นตัวอย่างชั้นดีของการปรับแต่งให้เหมาะกับระดับระบบ ฮาร์ดแวร์ และ OS ที่มีคุณค่าสำหรับการศึกษาและอ้างอิง
1 ความคิดเห็น
ความคิดเห็นใน Hacker News
มีการลองตรวจสอบข้ออ้างที่ว่า MacBook M4 Max ที่ฉันใช้อยู่จะติดอันดับท็อป 50 ของซูเปอร์คอมพิวเตอร์ TOP500 ในปี 2009
การจะติด TOP500 ในปี 2009 ต้องมีสมรรถนะเกิน 75 TFlop/s
M4 Max ทำได้ 18.4 TFlop/s ที่ FP32 แต่ TOP500 ใช้ FP64 (LINPACK)
อ้างอิงจากเบนช์มาร์กของ M2 แล้ว FP64 อยู่ที่ประมาณ 1/4 ของ FP32 ดังนั้นคาดว่าอยู่ราว 9 TFlop/s
ระดับนั้นยังไม่พอจะติด TOP500 ปี 2009
ดู รายชื่อ TOP500 ปี 2009
ถ้าแต่ละการเชื่อมต่อทำงาน I/O หลายอย่างพร้อมกัน ก็ต้องคูณด้วยจำนวนการเชื่อมต่อเป็นหลักพัน
เคยได้ยินว่าบนเซิร์ฟเวอร์ เวลาราว 95% หมดไปกับการรอ I/O แต่จริง ๆ นั่นหมายถึงในระดับเธรดเดี่ยว ไม่ใช่ทั้งเซิร์ฟเวอร์
เซิร์ฟเวอร์จริงมักมีการใช้ CPU ขึ้นไปถึง 70~80% อยู่บ่อย ๆ (มากกว่านี้ tail latency จะเริ่มแย่ลงอย่างรวดเร็ว)
ถ้าใช้ CPU แค่ 5% ตอน full load แปลว่ามีปัญหาเรื่องจำนวนโปรเซสขนานไม่พอหรือหน่วยความจำไม่พอ
จะว่าไปก็เป็นรายละเอียดเล็ก ๆ ทางเทคนิค แต่ความผิดพลาดแบบนี้ทำให้ความน่าเชื่อถือของโพสต์ลดลงได้ (พูดในฐานะแฟนของ Bun)
สรุปนั้นให้ความรู้สึกเหมือนภาพหลอนที่ LLM สร้างขึ้นมา
โดยเฉพาะช่วงสรุปที่ดูเหมือนดึงออกมาจาก LLM
"มันทำให้เข้าใจว่าตัวจัดการแพ็กเกจที่ถูกนำมาเบนช์มาร์กไม่ได้ผิดพลาด แต่เป็นคำตอบที่เหมาะกับยุคนั้น"
"สิ่งที่ Bun ทำนั้นไม่ใช่ความปฏิวัติ แต่เป็นผลจากการมองอย่างตรงไปตรงมาว่าอะไรทำให้ระบบช้าลงในปัจจุบัน"
"การที่การติดตั้งแพ็กเกจเร็วขึ้น 25 เท่าไม่ใช่เวทมนตร์ แต่เป็นสิ่งที่เกิดขึ้นตามธรรมชาติเมื่อสร้างเครื่องมือให้เหมาะกับฮาร์ดแวร์ยุคใหม่"
ถึงจะเป็นหัวข้อซับซ้อน แต่ก็อธิบายได้อ่านง่ายและเรียบง่ายมาก ชอบมาก
ยังน่าทึ่งเสมอที่ยังมีคนเปี่ยมแพสชันคอยท้าทายสภาพเดิมและลงมือแก้ปัญหาที่ยาก
ทุกเดือนฮาร์ดแวร์คอมพิวเตอร์ดีขึ้น แต่ซอฟต์แวร์กลับช้าลงเรื่อย ๆ มันให้ความรู้สึกผิดปกติ
อยากให้ทุกคนเขียนโค้ดอย่างมีประสิทธิภาพได้ดีกว่านี้
Zig เป็นภาษาที่ใหม่มาก และก็น่าสนใจที่ได้เห็นมันถูกใช้งานจริงอย่างจริงจัง
เพิ่งลองใช้ bun ครั้งแรกและประทับใจมาก
เพราะมีทั้งเซิร์ฟเวอร์ในตัวและ SQLite ในตัว แค่ติดตั้ง bun ตัวเดียวก็พอ ทำให้พัฒนาง่ายขึ้นมาก
ปกติใช้แค่ vanilla js และก็ไม่ค่อยชอบ ecosystem ของ node อยู่แล้ว เลยรู้สึกว่าน่าจะลอง bun ให้เร็วกว่านี้
ฉันลอง Bun มาหลายครั้งแล้ว และประสบการณ์ใช้งานก็น่าพอใจมาก
รู้สึกดีกว่า Node เสียอีก
แต่ก็มักจะเจอปัญหาสำคัญจนสุดท้ายต้องกลับไปใช้ Node
ตอนแรกคือโมดูล crypto ยังไม่เข้ากันกับ Nodejs (ตอนนี้แก้แล้ว) แล้วต่อมาก็เป็น Playwright ที่รันบน Bun ไม่ได้
ตอนนี้ Node เองก็มีเซิร์ฟเวอร์ในตัวและรองรับ SQLite แล้ว
ถ้าต้องการความสามารถมากกว่านั้น Hono ก็เป็นทางเลือกที่ดี
ยังไม่ค่อยเข้าใจส่วนในบทความที่อธิบายว่า hardlink ของ Linux กับ clonefile ของ MacOS เทียบเท่ากัน
ในกรณีของ hardlink ถ้าแก้ไขสำเนาหนึ่ง ไฟล์ในทุกโปรเจ็กต์จะเปลี่ยนตามอย่างไม่คาดคิดไม่ใช่หรือ
แม้จะเป็นคำอธิบายที่ค่อนข้างซับซ้อนทางเทคนิค แต่ก็เขียนได้อ่านง่ายและสนุกมากจนน่าทึ่ง
ฉันดูผลงานและวิดีโอของเธอมาเยอะ และรู้สึกได้เลยว่าเธอเตรียมตัวมาอย่างลึกซึ้ง
ถ้ามีเวลา แนะนำบทความและคอนเทนต์ YouTube ของเธออย่างยิ่ง
ช่วงหลัง ๆ เหมือนกิจกรรมจะน้อยลง น่าจะเพราะงานปัจจุบันของเธอ
ในส่วน Binary Manifest Caching ดูเหมือนเวลาเบนช์มาร์กของ "npm (cached)" จะหายไป
มีแค่ bun, bun (cached), npm และสถิติสรุปก็ดูเหมือนจะไม่ตรงกันด้วย
ฉันชอบสำนวนของโพสต์นี้มาก
รู้สึกว่านี่น่าจะนำไปใช้ใหม่เป็นตัวอย่างที่ดีมากในการอธิบายความสำคัญของ io_uring
สงสัยว่าอัปเดต io ล่าสุดใน Zig v0.15 จะช่วยเพิ่มประโยชน์ด้านประสิทธิภาพให้ Bun ได้อีกหรือไม่
ติดตามคาดหวังกับ bun มานานกว่าหนึ่งปีแล้ว
คิดว่าปี 2025 จะเป็นปีแรกที่ bun เข้าสู่กระแสหลัก แต่ผิดคาดที่มันยังไม่ดังขนาดนั้น
ในเรโปยอดนิยม 100,000 อันดับแรกบน GitHub ณ ปี 2025 เรโปใหม่ยังใช้ npm มากกว่า 35 เท่า และ pnpm มากกว่า 11 เท่า
แม้แต่ Deno ก็ไม่ได้ได้รับความนิยมสูงอย่างที่คิด
เลยสงสัยว่าเพราะอะไร
เป็นเพราะ runtime ทำให้เข้ากันได้ยากกว่าตัวจัดการแพ็กเกจหรือเปล่า
อยากฟังความเห็นจากคนที่เคยลอง bun แล้วสุดท้ายไม่เลือกใช้
สถิติอ้างอิงที่เกี่ยวข้อง
คอมเมนต์ HN ที่เกี่ยวข้อง
ฉันอยากชอบทั้ง Bun และ Deno เลยลองหลายครั้ง แต่สุดท้ายก็เจอบั๊กหนักจนใช้ต่อไม่ได้
ปัญหาใหญ่สุดที่เพิ่งเจอใน Bun คือสตรีมปิดก่อนเวลา
ลิงก์ issue ที่เกี่ยวข้อง
ส่วนใน Deno ฉันเจอปัญหา memory leak
ลิงก์ issue ที่เกี่ยวข้อง
สุดท้ายเลยรู้สึกว่า ecosystem ของ Node น่าจะรับเอาข้อดีของ Bun/Deno ไปใช้ได้ก่อน
Bun เป็นผู้ท้าชิงหน้าใหม่ที่มีเงินทุน venture capital ก้อนใหม่ เข้ามาแข่งกับผลิตภัณฑ์โอเพนซอร์สกระแสหลักที่พิสูจน์ตัวเองแล้วอย่าง Node
มีแรงจูงใจเรื่อง lock-in และท้ายที่สุดก็ไม่ได้ต่างจาก Node อย่างมีนัยสำคัญในระดับรากฐาน
จุดแข็งเชิงกลยุทธ์ก็ไม่ชัด และไม่ได้มอบอะไรใหม่ที่ Node ทำไม่ได้
ในทางปฏิบัติฉันยังไม่เคยเห็นกรณีใช้งานจริงจัง มีแต่กรณีใช้งานแบบเบา ๆ
ดูจาก issue tracker แล้ว ภาษาที่ใช้คือ Zig ดูไม่ค่อยปลอดภัย เลยเหมือนจะเกิด crash บ่อย
ฉันคงอยู่กับ Node ต่อไป
ฉันก็อยากฟังความเห็นจากคนอื่นเหมือนกัน
ในมุมมองฉัน Node เป็นโปรเจ็กต์ที่โตเต็มที่ มีความเป็นประชาธิปไตย และขับเคลื่อนโดยชุมชนมากกว่า
เพราะมันผ่านเหตุการณ์แตก fork เป็น io.js มาได้อย่างดี
ตรงกันข้าม bun กับ deno ต่างก็เป็นโปรเจ็กต์ที่ได้รับการสนับสนุนจาก VC เลยไม่ค่อยให้ความรู้สึกว่าเป็นชุมชนขับเคลื่อนแบบประชาธิปไตย
ฉันเป็นแฟนตัวยงของ Bun
ใช้ Bun กับทุกโปรเจ็กต์ที่ใช้ได้ และสคริปต์ one-off ต่าง ๆ ก็เขียนด้วย Bun/TS
แต่ก็ยังมี issue อยู่ไม่กี่อย่างที่น่ากังวล เลยยังลังเลจะ deploy production
ตัวอย่างเช่น ตอนรัน Express เว็บเซิร์ฟเวอร์ธรรมดาใน Docker ถ้าใช้ bun จะมีอาการค้าง
แต่พอเปลี่ยนเป็น node อย่างเดียวกลับทำงานปกติ
เมื่อปีก่อนก็เคยมีปัญหาเซิร์ฟเวอร์ล่มเพราะ memory leak ในชุด Bun + Prisma (เดาว่าตอนนี้น่าจะแก้แล้ว)
ถึงอย่างนั้น Bun ก็ดีมากจนต่อให้ต้องยอมรับข้อเสียพวกนี้ มันก็ยังช่วยลดเวลาในการพัฒนาโดยรวมได้
ความสะดวกในเรื่อง transpile, module, workspace และอื่น ๆ นั้นมหาศาล
ที่มันยังไม่แพร่หลายเท่า npm ก็เป็นเรื่องที่เข้าใจได้เต็มที่
อ่านบทความนี้แล้วเพลิดเพลินมาก
เป็นตัวอย่างที่ดีว่าหลักการวิทยาการคอมพิวเตอร์สำคัญแค่ไหนในงานพัฒนาซอฟต์แวร์จริง
ทั้ง Big O, locality ของเวลา/พื้นที่, algorithmic complexity, user/kernel space, file system, copy-on-write ฯลฯ
ในการพัฒนาแพ็กเกจระดับล่างแบบนี้ แนวคิดแทบทุกอย่างที่เรียนในหลักสูตร CS ถูกนำมาใช้จริง
CS ศึกษาเรื่องการคำนวณและทฤษฎี (ภาษาโปรแกรม, อัลกอริทึม, การเข้ารหัส, แมชชีนเลิร์นนิง ฯลฯ)
ส่วน SE คือการประยุกต์หลักวิศวกรรมเพื่อสร้างซอฟต์แวร์ที่ขยายได้และเชื่อถือได้
ฉันยังไม่ค่อยเข้าใจว่าทำไมการรอให้อ่านไฟล์บีบอัดจบก่อนแล้วค่อยแตกไฟล์ถึงได้เปรียบ
เดาว่าการเริ่มแตกไฟล์ตั้งแต่ก่อนดาวน์โหลดเสร็จน่าจะคุ้มกว่า แม้จะเสียเปรียบจากการคัดลอกข้อมูลในหน่วยความจำเพิ่มขึ้นก็ตาม