มันไม่มีประสิทธิภาพที่จะต่อสตริงเข้าทีละตัว?


11

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

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


ปล่อยให้คอมไพเลอร์และผู้สร้างโปรไฟล์พวกเขาจะดูแลเกี่ยวกับมันเวลาของคุณมีราคาแพงกว่าเวลามากในการเรียงสตริง
OZ_

7
ขึ้นอยู่กับการนำไปใช้งาน - คุณควรตรวจสอบเอกสารประกอบสำหรับสตริงไลบรารีเฉพาะของคุณ เป็นไปได้ที่จะใช้สตริงที่เชื่อมต่อกันโดยอ้างอิงในเวลา O (1) ไม่ว่าในกรณีใดถ้าคุณต้องการเชื่อมโยงรายการสตริงที่มีความยาวโดยพลการคุณควรใช้คลาสหรือฟังก์ชั่นที่ออกแบบมาสำหรับสิ่งนี้
Comingstorm

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

@Caleb ระบบปฏิบัติการมีส่วนร่วมในการจัดสรรหน่วยความจำทั้งหมด การไม่ปฏิบัติตามกฎนี้เป็นประเภทหน่วยความจำรั่ว ข้อยกเว้นคือเมื่อคุณมีสตริงที่ฮาร์ดโค้ดในแอปพลิเคชัน สิ่งเหล่านั้นถูกเขียนเป็นข้อมูลไบนารีภายในชุดประกอบที่สร้างขึ้น แต่ทันทีที่คุณจัดการ (หรืออาจกำหนด) สตริงมันจะต้องถูกเก็บไว้ในหน่วยความจำ (นั่นคือต้องมีการจัดสรรหน่วยความจำ)
JSideris

4
@Bizorke ในสถานการณ์ทั่วไปตัวจัดสรรหน่วยความจำเช่น malloc () (ซึ่งเป็นส่วนหนึ่งของไลบรารีมาตรฐาน C ไม่ใช่ OS) ใช้เพื่อจัดสรรหน่วยความจำต่าง ๆ จากหน่วยความจำที่จัดสรรให้กับกระบวนการแล้วโดยระบบปฏิบัติการ ระบบปฏิบัติการไม่จำเป็นต้องมีส่วนร่วมเว้นแต่กระบวนการจะมีหน่วยความจำเหลือน้อยและจำเป็นต้องร้องขอเพิ่มเติม นอกจากนี้ยังอาจมีส่วนร่วมในระดับที่ต่ำกว่าหากการจัดสรรทำให้เกิดความผิดพลาดของหน้า ดังนั้นใช่แล้วระบบปฏิบัติการจะให้หน่วยความจำในท้ายที่สุด แต่ก็ไม่จำเป็นต้องมีส่วนร่วมในการจัดสรรสตริงและวัตถุอื่น ๆ ในกระบวนการ
Caleb

คำตอบ:


21

คำอธิบายของคุณว่าทำไมมันไม่มีประสิทธิภาพมีความถูกต้องอย่างน้อยในภาษาที่ฉันคุ้นเคยกับ (C, Java, C #) แม้ว่าฉันจะไม่เห็นด้วยที่มันเป็นเรื่องปกติในระดับสากลเพื่อทำการเรียงต่อกันของสตริงจำนวนมาก ในรหัส C # ที่ฉันทำงานมีการใช้งานจำนวนStringBuilderมากString.Formatและอื่น ๆ ซึ่งเป็นหน่วยความจำที่บันทึก techiniques เพื่อหลีกเลี่ยงการจัดสรรซ้ำ

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

หากส่วนใหญ่นักพัฒนาสตริงเชื่อมโยงเป็นฐานคำตอบของพวกหมดจดในประสบการณ์ส่วนใหญ่จะบอกว่ามันไม่เคยทำให้ความแตกต่างและจะหลีกเลี่ยงการใช้เครื่องมือดังกล่าวในความโปรดปรานของ for (int i=0; i<1000; i++) { strA += strB; }"อ่านได้มากขึ้น" แต่พวกเขาไม่เคยวัดมัน

คำตอบที่แท้จริงสำหรับคำถามนี้สามารถพบได้ในคำตอบดังนั้นนี้ซึ่งแสดงให้เห็นว่าในหนึ่งตัวอย่างเช่นเมื่อเชื่อมโยง 50,000 สตริง (ซึ่งขึ้นอยู่กับโปรแกรมของคุณอาจจะเป็นที่เกิดขึ้นร่วมกัน) แม้คนเล็กส่งผลให้ประสิทธิภาพตี 1000x

หากการแสดงนั้นไม่ได้มีความหมายอะไรเลยเลย แต่ฉันไม่เห็นด้วยว่าการใช้ทางเลือก (StringBuilder) นั้นยากหรืออ่านได้น้อยกว่าและดังนั้นจึงเป็นวิธีการเขียนโปรแกรมที่สมเหตุสมผลซึ่งไม่ควรเรียกใช้การป้องกัน "การเพิ่มประสิทธิภาพก่อนวัยอันควร"

UPDATE:

ผมคิดว่าสิ่งนี้จะลงมาให้เป็นรู้ว่าแพลตฟอร์มของคุณและปฏิบัติตามวิธีปฏิบัติที่ดีที่สุดซึ่งเป็นเศร้าไม่เป็นสากล ตัวอย่างสองตัวอย่างจาก "ภาษาสมัยใหม่" ที่ต่างกัน:

  1. ในคำตอบ SO อื่นที่ตรงข้ามแน่นอนลักษณะการปฏิบัติงาน (array.join VS + =) พบว่ามีบางครั้งที่แท้จริงในการใช้ JavaScript ในบางเบราว์เซอร์การต่อข้อมูลสตริงจะได้รับการปรับให้เหมาะสมโดยอัตโนมัติและในกรณีอื่น ๆ ดังนั้นข้อเสนอแนะ (อย่างน้อยก็ในคำถาม SO นั้น) คือการต่อกันและไม่ต้องกังวล
  2. ในกรณีอื่นคอมไพเลอร์Java สามารถแทนที่การต่อข้อมูลอัตโนมัติด้วยโครงสร้างที่มีประสิทธิภาพมากขึ้นเช่น StringBuilder อย่างไรก็ตามตามที่คนอื่น ๆ ชี้ว่าสิ่งนี้เป็นแบบไม่แน่นอนไม่รับประกันและการใช้ StringBuilder จะไม่ทำร้ายความสามารถในการอ่าน ในกรณีพิเศษนี้ฉันมักจะแนะนำให้ใช้การต่อข้อมูลขนาดใหญ่สำหรับคอลเลกชันขนาดใหญ่หรืออาศัยการทำงานของคอมไพเลอร์ Java แบบไม่กำหนดเวลา ในทำนองเดียวกันใน .NET, การเพิ่มประสิทธิภาพของการจัดเรียงไม่ได้ดำเนินการเลยทีเดียว

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


-1: มี BS ที่สำคัญ strA + strBคือว่าเหมือนกับการใช้ StringBuilder มันมีประสิทธิภาพที่ยอดเยี่ยมถึง 1x หรือ 0x ขึ้นอยู่กับว่าคุณวัดอย่างไร สำหรับรายละเอียดเพิ่มเติม, codinghorror.com/blog/2009/01/...
อมรา

5
@sparkleshy: ฉันเดาว่าคำตอบ SO ใช้ Java และบทความที่เชื่อมโยงของคุณใช้ C # ฉันเห็นด้วยกับผู้ที่พูดว่า "ขึ้นอยู่กับการใช้งาน" และ "วัดสำหรับสภาพแวดล้อมของคุณโดยเฉพาะ"
ไก่ชาน

1
@KaiChan: string concatenation นั้นเหมือนกันใน java และ c #
amara

3
@sparkleshy - จุดที่ใช้ แต่การใช้ StringBuilder, String.Join ฯลฯ เพื่อเชื่อมโยงสองสายอย่างแน่นอนไม่ค่อยแนะนำ นอกจากนี้คำถามของ OP นั้นเกี่ยวข้องกับ "เนื้อหาของคอลเลกชันที่ถูกรวมเข้าด้วยกัน" โดยเฉพาะซึ่งไม่ใช่กรณี (ที่ StringBuilder ฯลฯ มีผลบังคับใช้มาก) ไม่ว่าฉันจะอัปเดตตัวอย่างของฉันให้มากกว่าเดิม
Kevin McCormick

3
ฉันไม่สนใจภาษาเพื่อจุดประสงค์ของคำถามนี้ การใช้โปรแกรมสร้างสตริงที่อยู่เบื้องหลังในบางภาษาอธิบายว่าทำไมการเชื่อมโยงรายการสตริงทั้งหมดจึงไม่มีประสิทธิภาพที่จะตอบคำถามของฉัน คำตอบนี้ไม่ได้อธิบายว่าการเข้าร่วมรายการอาจเป็นอันตรายและผู้สร้างสตริงที่แนะนำเป็นทางเลือก ฉันแนะนำให้เพิ่มการใช้ stringbuilder ของคอมไพเลอร์เบื้องหลังคำตอบของคุณเพื่อหลีกเลี่ยงการสูญเสียชื่อเสียงหรือการตีความที่ผิด
JSideris

2

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

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


1

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


3
ไม่ใช่กฎที่มีประสิทธิภาพมากฉันคิดว่า
OZ_

@OZ_: นี่เป็นคำพูดที่ใช้กันอย่างแพร่หลาย (Michael A. Jackson) และอื่น ๆ โดยชอบของ Donald Knuth ... จากนั้นก็มีอันนี้ที่ฉันมักจะละเว้นจากการใช้ "บาปคอมพิวเตอร์มากขึ้นมีความมุ่งมั่นในชื่อของประสิทธิภาพ ( โดยไม่จำเป็นต้องทำให้สำเร็จ) นอกเหนือจากเหตุผลเดียวอื่น ๆ - รวมถึงความโง่เขลาตาบอด "
mattnz

2
ฉันควรจะชี้ให้เห็นว่าไมเคิลเอลแจ็คสันเป็น Brit จึงของการเพิ่มประสิทธิภาพไม่ได้เพิ่มประสิทธิภาพ ในบางจุดที่ผมควรจะแก้ไขหน้าวิกิพีเดีย * 8 ')
ทำเครื่องหมายบูธ

ฉันเห็นด้วยอย่างยิ่งคุณควรแก้ไขข้อผิดพลาดการสะกดคำเหล่านั้น แม้ว่าภาษาแม่ของฉันคือภาษาอังกฤษของควีนส์ แต่ฉันพบว่ามันง่ายที่จะพูดกับเราทางอินทราเว็บ .......
mattnz

ไม่มีใครคิดเกี่ยวกับผู้ใช้ คุณอาจทำให้นักพัฒนาสร้างเร็วขึ้นเล็กน้อย แต่จากนั้นลูกค้าของคุณทุกคนต้องทนทุกข์กับมัน เขียนรหัสสำหรับพวกเขาไม่ใช่สำหรับคุณ
gbjbaanb

1

มันยากมากที่จะพูดอะไรเกี่ยวกับประสิทธิภาพโดยไม่มีการทดสอบภาคปฏิบัติ เมื่อเร็ว ๆ นี้ฉันรู้สึกประหลาดใจมากที่พบว่าใน JavaScript การเชื่อมต่อสตริงไร้เดียงสามักเร็วกว่าโซลูชัน "สร้างรายการและเข้าร่วม" ที่แนะนำ (ทดสอบที่นี่เปรียบเทียบ t1 กับ t4) ฉันยังงงว่าทำไมถึงเกิดขึ้น

คำถามสองสามข้อที่คุณอาจถามเมื่อให้เหตุผลเกี่ยวกับประสิทธิภาพ (โดยเฉพาะเกี่ยวกับการใช้หน่วยความจำ) คือ: 1) อินพุตของฉันมีขนาดเท่าใด 2) คอมไพเลอร์ของฉันมันฉลาดแค่ไหน? 3) runtime ของฉันจัดการหน่วยความจำได้อย่างไร? นี่ไม่ใช่ความละเอียดถี่ถ้วน แต่เป็นจุดเริ่มต้น

  1. อินพุตของฉันใหญ่แค่ไหน

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

  2. คอมไพเลอร์ของฉันช่างฉลาดเหลือเกิน

    คอมไพเลอร์จำนวนมากฉลาดพอที่จะ "ปรับค่า" ตัวแปรที่เขียนไป แต่ไม่เคยอ่าน ในทำนองเดียวกันคอมไพเลอร์ที่ดีอาจสามารถแปลงการรวมสตริงที่ไร้เดียงสาเป็นการใช้งานไลบรารี่ (คอร์) และหากหลาย ๆ ไฟล์ถูกสร้างขึ้นโดยไม่มีการอ่านใด ๆ ก็ไม่จำเป็นต้องแปลงมันกลับเป็นสตริงระหว่างการดำเนินการเหล่านั้น ซอร์สโค้ดของคุณดูเหมือนจะทำอย่างนั้น) ฉันไม่สามารถบอกได้ว่าคอมไพเลอร์ตัวใดทำอย่างนั้นหรือไม่ว่าจะทำเท่าไร (อย่างน้อย Java AFAIK แทนที่ concats หลายตัวในนิพจน์เดียวกันเป็นลำดับของการดำเนินการ StringBuffer) แต่ก็เป็นไปได้

  3. รันไทม์ของฉันจัดการหน่วยความจำได้อย่างไร

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

    วิธีหนึ่งที่จะมีผลต่อการเรียงสตริง (ข้อจำกัดความรับผิดชอบ: นี่คือการคาดเดาอย่างฉับพลันฉันไม่มีความรู้เพียงพอที่จะพูดได้อย่างแน่นอน) จะเป็นอย่างไรถ้าหน่วยความจำสำหรับnaïveถูกจัดสรรใกล้กับส่วนที่เหลือของรหัสที่ใช้ หากจัดสรรและเผยแพร่หลายครั้ง) ในขณะที่หน่วยความจำสำหรับวัตถุห้องสมุดได้รับการจัดสรรไกลจากมัน (ดังนั้นการเปลี่ยนแปลงบริบทจำนวนมากในขณะที่การคำนวณรหัสของคุณห้องสมุดใช้งานรหัสของคุณคำนวณมากขึ้น ฯลฯ จะทำให้แคชหายไป) แน่นอนว่าสำหรับอินพุตขนาดใหญ่ OTOH การพลาดแคชจะเกิดขึ้นต่อไปดังนั้นปัญหาของการจัดสรรหลายครั้งจะเด่นชัดมากขึ้น

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


ใช่ฉันเห็นด้วยว่านี่เป็นพื้นที่ที่ผู้รวบรวมสามารถเข้าใจได้ในทางทฤษฎีว่าคุณกำลังพยายามเพิ่มสตริงเข้าด้วยกันและจากนั้นปรับให้เหมาะสมราวกับว่าคุณกำลังใช้ตัวสร้างสตริง อย่างไรก็ตามนี่เป็นสิ่งที่ทำไม่ได้และฉันไม่คิดว่ามันจะถูกนำไปใช้ในคอมไพเลอร์สมัยใหม่ คุณเพิ่งให้แนวคิดที่ยอดเยี่ยมสำหรับโครงการวิจัยระดับปริญญาตรี: D
JSideris

ตรวจสอบคำตอบนี้คอมไพเลอร์ Java ใช้แล้วStringBuilderภายใต้ประทุนทั้งหมดที่จะต้องทำคือไม่เรียกtoStringจนกระทั่งตัวแปรจำเป็นจริงๆ ถ้าฉันจำได้อย่างถูกต้องมันเป็นเช่นนั้นสำหรับนิพจน์เดียวความสงสัยเพียงอย่างเดียวของฉันคือว่ามันใช้กับหลาย ๆ คำสั่งในวิธีการเดียวกันหรือไม่ ฉันไม่รู้อะไรเกี่ยวกับ. NET internals แต่ฉันเชื่อว่ากลยุทธ์ที่คล้ายกันอาจถูกใช้โดยคอมไพเลอร์ C # เช่นกัน
mgibsonbr

0

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


-1

มันไม่มีประสิทธิภาพที่จะต่อสตริงเข้าทีละตัว?

เลขที่

คุณได้อ่าน'โศกนาฏกรรมที่น่าเศร้าของ Micro-Optimization Theatre'หรือยัง?


4
"การเพิ่มประสิทธิภาพก่อนวัยอันควรเป็นรากฐานของความชั่วร้ายทั้งหมด" - Knuth
Scott C Wilson

4
รากของความชั่วร้ายทั้งหมดในการปรับให้เหมาะสมคือการใช้วลีนี้โดยไม่มีบริบท
OZ_

แค่พูดอะไรบางอย่างที่เป็นจริงโดยไม่ให้เหตุผลสนับสนุนบางอย่างไม่มีประโยชน์ในฟอรัมเช่นนี้
Edward Strange

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