Ten Pounds in a Five-Pound Sack

"ผู้เขียนควรจะมองที่โนอาห์ และ... เรียนรู้อย่างที่พวกเขาทำในเรืออาร์ค คือการเบียดเสียดสิ่งของจำนวนมากลงในพื้นที่ขนาดเล็กมาก" — ซิดนีย์ สมิธ (SYDNEY SMITH), EDINBURGH REVIEW

ภาพแกะสลักจากภาพวาดโดย Heywood Hardy

Program Space as Cost

มันมีขนาดใหญ่แค่ไหน? นอกจากเวลารันแล้ว พื้นที่ที่โปรแกรมครอบครองก็คือต้นทุนหลัก ข้อนี้เป็นจริงแม้แต่กับโปรแกรมที่มีลิขสิทธิ์ ซึ่งผู้ใช้ต้องจ่ายค่าธรรมเนียมให้ผู้เขียน ซึ่งจริงๆ แล้วก็คือการแบ่งเบาต้นทุนการพัฒนา ลองพิจารณาระบบซอฟต์แวร์แบบโต้ตอบ IBM APL ซึ่งมีค่าเช่า 400 ดอลลาร์ต่อเดือน และเมื่อใช้งาน มันจะใช้หน่วยความจำอย่างน้อย 160 K ไบต์ ในเครื่อง Model 165 ค่าเช่าหน่วยความจำอยู่ที่ประมาณ 12 ดอลลาร์ต่อกิโลไบต์ต่อเดือน หากเปิดใช้งานโปรแกรมตลอดเวลา เราจะต้องจ่ายค่าเช่าซอฟต์แวร์ 400 ดอลลาร์ และค่าเช่าหน่วยความจำ 1,920 ดอลลาร์สำหรับการใช้โปรแกรม หากใช้งานระบบ APL เพียงสี่ชั่วโมงต่อวัน ต้นทุนจะเป็นค่าเช่าซอฟต์แวร์ 400 ดอลลาร์ และค่าเช่าหน่วยความจำ 320 ดอลลาร์ต่อเดือน

เรามักจะได้ยินความรู้สึกตกใจว่าเครื่องขนาด 2 M ไบต์ อาจมีพื้นที่ถึง 400 K ที่ถูกใช้ไปกับระบบปฏิบัติการ นี่เป็นเรื่องที่น่าขำพอๆ กับการวิจารณ์เครื่องบิน Boeing 747 เพราะมันมีราคา 27 ล้านดอลลาร์ เราต้องถามด้วยว่า "มันทำอะไรได้บ้าง?" สิ่งที่ได้รับในแง่ของความง่ายในการใช้งานและประสิทธิภาพ (ผ่านการใช้ทรัพยากรระบบอย่างมีประสิทธิภาพ) นั้นคุ้มค่ากับเงินที่จ่ายไปหรือไม่? เงินจำนวน 4,800 ดอลลาร์ต่อเดือนที่ลงทุนไปกับค่าเช่าหน่วยความจำนั้นสามารถนำไปใช้กับฮาร์ดแวร์อื่น นักเขียนโปรแกรม หรือโปรแกรมประยุกต์ให้เกิดประโยชน์มากกว่านี้ได้หรือไม่? ผู้ออกแบบระบบจะแบ่งส่วนหนึ่งของทรัพยากรฮาร์ดแวร์ทั้งหมดไปไว้ในหน่วยความจำสำหรับโปรแกรมที่ฝังตัวอยู่ (resident-program memory) เมื่อเขาคิดว่ามันจะสร้างประโยชน์ให้ผู้ใช้ในรูปแบบนั้นได้มากกว่าการนำไปใช้เป็นตัวบวกเลขหรือดิสก์ การทำเป็นอย่างอื่นถือเป็นความไม่รับผิดชอบอย่างยิ่ง และผลลัพธ์ที่ได้จะต้องถูกตัดสินในภาพรวม ไม่มีใครสามารถวิจารณ์ระบบการเขียนโปรแกรมเพียงเพราะขนาดของมัน และในขณะเดียวกันก็สนับสนุนการรวมงานออกแบบฮาร์ดแวร์และซอฟต์แวร์เข้าด้วยกันอย่างใกล้ชิด

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

Size Control

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

ใน OS/360 แม้ว่าสิ่งเหล่านี้จะถูกทำอย่างระมัดระวังมาก แต่ก็ยังมีบทเรียนอื่นๆ ที่ต้องเรียนรู้อย่างเจ็บปวด ประการแรก การกำหนดเป้าหมายขนาดสำหรับหน่วยความจำหลัก (core) นั้นไม่เพียงพอ เราต้องกำหนดงบประมาณสำหรับขนาดในทุกแง่มุม ในระบบปฏิบัติการรุ่นก่อนๆ ส่วนใหญ่ ระบบจะจัดเก็บไว้ในเทป และเวลาในการค้นหาที่นานของเทปหมายความว่าเราจะไม่ถูกหลอกล่อให้เรียกใช้ส่วนของโปรแกรมเข้ามาบ่อยๆ แต่ OS/360 นั้นจัดเก็บในดิสก์ เช่นเดียวกับรุ่นก่อนหน้าอย่างระบบปฏิบัติการ Stretch และระบบปฏิบัติการดิสก์ 1410-7010 ผู้สร้างต่างยินดีในเสรีภาพของการเข้าถึงดิสก์ที่มีราคาถูก ผลลัพธ์ในช่วงแรกนั้นหายนะต่อประสิทธิภาพอย่างยิ่ง

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

โชคดีที่มีวันหนึ่งในช่วงแรกของการพัฒนา โปรแกรมจำลองประสิทธิภาพ (performance simulator) ของ OS/360 เริ่มทำงาน ผลลัพธ์แรกชี้ให้เห็นถึงปัญหาใหญ่ Fortran H ในเครื่อง Model 65 ที่ใช้ดรัม (drums) จำลองการคอมไพล์ได้ที่ความเร็วเพียงห้าคำสั่งต่อนาที! การขุดคุ้ยข้อมูลแสดงให้เห็นว่าโมดูลโปรแกรมควบคุมแต่ละตัวมีการเข้าถึงดิสก์บ่อยครั้งมาก แม้แต่ส่วนที่มีความถี่สูง...

โมดูลตัวควบคุม (supervisor modules) ก็เข้าถึงดิสก์บ่อยครั้งมาก และผลที่ได้ก็คล้ายคลึงกับการเกิด page thrashing (การสลับหน้าหน่วยความจำจนงานไม่เดิน) บทเรียนแรกนั้นชัดเจน: จงกำหนดงบประมาณขนาดโดยรวมควบคู่ไปกับงบประมาณพื้นที่ส่วนที่ฝังตัวอยู่ (resident-space budgets) และกำหนดงบประมาณการเข้าถึงสื่อจัดเก็บข้อมูลสำรอง (backing-store accesses) ควบคู่ไปกับขนาดด้วย

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

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

Space Techniques

การกำหนดงบประมาณและการควบคุมพื้นที่เพียงอย่างเดียวไม่สามารถทำให้โปรแกรมมีขนาดเล็กลงได้ สิ่งนั้นต้องอาศัยการประดิษฐ์สร้างสรรค์และฝีมือ ความจริงที่เห็นได้ชัดคือ หน้าที่การทำงานที่มากขึ้นหมายถึงพื้นที่ที่มากขึ้น หากความเร็วยังคงที่ ดังนั้นทักษะด้านฝีมือแขนงแรกคือการแลกเปลี่ยนหน้าที่การทำงานกับขนาด (trading function for size) ณ จุดนี้มีคำถามเชิงนโยบายที่ลึกซึ้งเกิดขึ้นตั้งแต่เนิ่นๆ ว่าเราควรจะเหลือทางเลือกนี้ไว้ให้ผู้ใช้มากน้อยเพียงใด? เราสามารถออกแบบโปรแกรมที่มีคุณสมบัติเสริมมากมาย โดยแต่ละอย่างใช้พื้นที่เพียงเล็กน้อย เราสามารถออกแบบตัวสร้างโปรแกรม (generator) ที่รับรายการตัวเลือกและปรับแต่งโปรแกรมตามนั้น แต่สำหรับชุดตัวเลือกเฉพาะใดๆ โปรแกรมที่มีโครงสร้างแบบรวมเป็นก้อนเดียว (monolithic program) จะใช้พื้นที่น้อยกว่า มันเหมือนกับรถยนต์ หากไฟอ่านแผนที่ ที่จุดบุหรี่ และนาฬิกา ถูกรวมราคาไว้เป็นแพ็กเกจตัวเลือกเดียว แพ็กเกจนั้นจะมีราคาถูกกว่าการที่สามารถเลือกแต่ละอย่างแยกกันได้ ดังนั้นผู้ออกแบบจึงต้องตัดสินใจว่าทางเลือกของผู้ใช้ในเรื่องคุณสมบัติเสริมนั้นควรจะละเอียดมากน้อยเพียงใด

ในการออกแบบระบบสำหรับช่วงขนาดหน่วยความจำที่หลากหลาย มีคำถามพื้นฐานอีกข้อหนึ่งเกิดขึ้น มีผลกระทบที่จำกัดช่วงความเหมาะสมไม่ให้ขยายกว้างออกไปได้ตามใจชอบ แม้จะมีการแบ่งหน้าที่การทำงานออกเป็นโมดูลที่ละเอียดก็ตาม ในระบบที่มีขนาดเล็กที่สุด โมดูลส่วนใหญ่จะถูกวางซ้อนกัน (overlaid) ส่วนสำคัญของพื้นที่ที่ฝังตัวอยู่ (resident space) ของระบบขนาดเล็กที่สุดจะต้องถูกจัดสรรไว้เป็นพื้นที่ชั่วคราว (transient area) หรือพื้นที่สลับหน้าหน่วยความจำ (paging area) ซึ่งส่วนอื่นๆ จะถูกดึงเข้ามาในพื้นที่นี้ ขนาดของพื้นที่นี้จะเป็นตัวกำหนดขนาดของโมดูลทั้งหมด และการแบ่งหน้าที่การทำงานออกเป็นโมดูลขนาดเล็กก็มีต้นทุนทั้งในด้านประสิทธิภาพและพื้นที่ ดังนั้นระบบขนาดใหญ่ซึ่งสามารถจัดหาพื้นที่ชั่วคราวได้ใหญ่กว่าถึงยี่สิบเท่า จึงช่วยลดการเข้าถึงข้อมูลลงได้เท่านั้น แต่มันก็ยังคงสูญเสียทั้งความเร็วและพื้นที่เนื่องจากขนาดของโมดูลนั้นเล็กเกินไป ผลกระทบนี้ได้จำกัดประสิทธิภาพสูงสุดของระบบที่จะสร้างขึ้นมาจากโมดูลของระบบขนาดเล็ก

ทักษะด้านฝีมือแขนงที่สองคือการแลกเปลี่ยนพื้นที่กับเวลา (space-time trade-offs) สำหรับหน้าที่การทำงานที่กำหนดให้ ยิ่งมีพื้นที่มากเท่าใด ก็ยิ่งเร็วขึ้นเท่านั้น ข้อนี้เป็นจริงในช่วงที่กว้างอย่างน่าทึ่ง ความเป็นจริงข้อนี้เองที่ทำให้การกำหนดงบประมาณพื้นที่เป็นสิ่งที่ทำได้จริง ผู้จัดการสามารถทำสองสิ่งเพื่อช่วยให้ทีมของเขาทำการแลกเปลี่ยนพื้นที่กับเวลาได้ดี สิ่งแรกคือการรับประกันว่าพวกเขาได้รับการฝึกฝนในเทคนิคการเขียนโปรแกรม...

ไม่ใช่แค่ปล่อยให้พึ่งพาไหวพริบดั้งเดิมและประสบการณ์ก่อนหน้าเพียงอย่างเดียว สำหรับภาษาหรือเครื่องจักรใหม่ เรื่องนี้สำคัญเป็นพิเศษ รายละเอียดเฉพาะของการใช้งานอย่างชำนาญจำเป็นต้องได้รับการเรียนรู้อย่างรวดเร็วและแบ่งปันกันอย่างกว้างขวาง อาจจะมีรางวัลพิเศษหรือคำชมเชยสำหรับเทคนิคใหม่ๆ สิ่งที่สองคือการยอมรับว่าการเขียนโปรแกรมก็มีเทคโนโลยี และส่วนประกอบต่างๆ จำเป็นต้องได้รับการประดิษฐ์ขึ้น ทุกโครงการต้องการสมุดบันทึกที่เต็มไปด้วยรูทีนย่อย (subroutines) หรือแมโคร (macros) ที่ดีสำหรับการเข้าคิว การค้นหา การทำแฮช (hashing) และการจัดเรียง สำหรับแต่ละฟังก์ชันดังกล่าว สมุดบันทึกควรมีโปรแกรมอย่างน้อยสองแบบ คือแบบที่เน้นความเร็ว และแบบที่เน้นการบีบอัดขนาด การพัฒนาเทคโนโลยีดังกล่าวนับเป็นงานในระดับการทำให้เป็นจริง (realization task) ที่สำคัญซึ่งสามารถทำควบคู่ไปกับสถาปัตยกรรมของระบบได้

Representation Is the Essence of Programming

นอกเหนือจากทักษะฝีมือคือการประดิษฐ์สร้างสรรค์ และจุดนี้เองที่โปรแกรมที่กระชับ เรียบง่าย และรวดเร็วจะถือกำเนิดขึ้น เกือบทุกครั้งสิ่งเหล่านี้เป็นผลมาจากความก้าวหน้าในระดับกลยุทธ์ (strategic breakthrough) มากกว่าความชาญฉลาดในระดับยุทธวิธี บางครั้งความก้าวหน้าทางกลยุทธ์อาจเป็นอัลกอริทึมใหม่ เช่น Cooley-Tukey Fast Fourier Transform หรือการแทนที่การจัดเรียงแบบ $n^2$ ด้วยการเปรียบเทียบแบบ $n \log n$ แต่บ่อยครั้งกว่านั้น ความก้าวหน้าทางกลยุทธ์จะมาจากการออกแบบการแสดงแทนข้อมูล (representation) หรือตารางใหม่ นี่คือจุดที่หัวใจของโปรแกรมตั้งอยู่ จงแสดงผังงาน (flowcharts) ของคุณและซ่อนตารางของคุณไว้ แล้วผมก็จะยังคงงุนงงต่อไป แต่จงแสดงตารางของคุณให้ผมเห็น และโดยปกติผมจะไม่ต้องการผังงานของคุณเลย เพราะมันจะชัดเจนในตัวเอง

มันง่ายที่จะยกตัวอย่างพลังของการแสดงแทนข้อมูล ผมจำได้ว่ามีชายหนุ่มคนหนึ่งรับหน้าที่สร้างตัวแปลคำสั่ง (interpreter) ที่ซับซ้อนสำหรับเครื่อง IBM 650 เขาลงเอยด้วยการบีบอัดมันลงในพื้นที่ขนาดเล็กอย่างไม่น่าเชื่อโดยการสร้าง "ตัวแปลภาษาสำหรับตัวแปลภาษา" (interpreter for the interpreter) โดยตระหนักว่าการปฏิสัมพันธ์ของมนุษย์นั้นช้าและไม่บ่อยนัก แต่พื้นที่หน่วยความจำนั้นมีค่ามาก คอมไพเลอร์ Fortran ขนาดเล็กและสง่างามของ Digitek ใช้การแสดงแทนข้อมูลที่หนาแน่นและเป็นแบบเฉพาะตัวมากสำหรับโค้ดของตัวคอมไพเลอร์เอง ทำให้ไม่จำเป็นต้องใช้สื่อจัดเก็บข้อมูลภายนอก เวลาที่สูญเสียไปในการถอดรหัสการแสดงแทนข้อมูลนี้ได้รับคืนมาสิบเท่าจากการที่ไม่ต้องทำการอ่านเขียนข้อมูล (input-output) (แบบฝึกหัดท้ายบทที่ 6 ในหนังสือ Automatic Data Processing โดย Brooks และ Iverson ได้รวบรวมตัวอย่างดังกล่าวไว้ เช่นเดียวกับแบบฝึกหัดมากมายของ Knuth)

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