มันแย่แค่ไหนที่เรียก println () บ่อยครั้งกว่าการต่อสตริงเข้าด้วยกันและเรียกมันครั้งเดียว?


23

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

ตัวอย่างเช่นมีประสิทธิภาพน้อยกว่าที่จะมี

System.out.println("Good morning.");
System.out.println("Please enter your name");

เมื่อเทียบกับ

System.out.println("Good morning.\nPlease enter your name");

ในตัวอย่างความแตกต่างเป็นเพียงการโทรหนึ่งครั้งprintln()แต่จะเกิดอะไรขึ้นถ้ามีมากกว่านั้น

ในหมายเหตุที่เกี่ยวข้องคำแถลงเกี่ยวกับการพิมพ์ข้อความอาจดูแปลกในขณะที่ดูซอร์สโค้ดหากข้อความที่จะพิมพ์ยาว ถ้าตัวอักษรไม่สามารถทำให้สั้นลงได้สิ่งที่สามารถทำได้? ควรเป็นกรณีที่มีการprintln()โทรหลายสายหรือไม่? มีคนเคยบอกฉันว่าบรรทัดของรหัสไม่ควรเกิน 80 อักขระ (IIRC) ดังนั้นคุณจะทำอย่างไร

System.out.println("Good morning everyone. I am here today to present you with a very, very lengthy sentence in order to prove a point about how it looks strange amongst other code.");

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


แม้ว่านี่จะเป็นรหัสน้อยมากฉันก็ต้องบอกว่าฉันสงสัยในสิ่งเดียวกัน จะดีเพื่อกำหนดคำตอบครั้งนี้และทั้งหมด
Simon Forsberg

@ SimonAndréForsbergฉันไม่แน่ใจว่ามันใช้ได้กับ Java เพราะมันทำงานบนเครื่องเสมือน แต่ในภาษาระดับต่ำกว่าเช่น C / C ++ ฉันคิดว่ามันจะมีค่าใช้จ่ายสูงในแต่ละครั้งที่มีบางสิ่งเขียนไปยังสตรีมเอาท์พุท จะต้องทำ

มีสิ่งนี้ให้พิจารณาด้วย: stackoverflow.com/questions/21947452/…
hjk

1
ฉันต้องบอกว่าฉันไม่เห็นประเด็นที่นี่ เมื่อมีการโต้ตอบกับผู้ใช้ผ่านทางเทอร์มินัลฉันไม่สามารถจินตนาการถึงปัญหาด้านประสิทธิภาพใด ๆ ได้เนื่องจากโดยปกติแล้วจะมีไม่มากนักที่จะพิมพ์ และแอปพลิเคชันที่มี GUI หรือเว็บแอพควรเขียนลงในไฟล์บันทึก (โดยปกติจะใช้เฟรมเวิร์ก)
Andy

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

คำตอบ:


29

มีแรงสองแรงที่นี่ในความตึงเครียด: ประสิทธิภาพเทียบกับความสามารถในการอ่าน

ลองแก้ไขปัญหาข้อที่สามก่อนเถอะเส้นยาว:

System.out.println("Good morning everyone. I am here today to present you with a very, very lengthy sentence in order to prove a point about how it looks strange amongst other code.");

วิธีที่ดีที่สุดในการใช้สิ่งนี้และให้อ่านง่ายคือการใช้การต่อสตริง:

System.out.println("Good morning everyone. I am here today to present you "
                 + "with a very, very lengthy sentence in order to prove a "
                 + "point about how it looks strange amongst other code.");

การต่อข้อมูลแบบสตริงคงที่จะเกิดขึ้นในเวลารวบรวมและจะไม่มีผลต่อประสิทธิภาพเลย บรรทัดสามารถอ่านได้และคุณสามารถไปต่อได้

ตอนนี้เกี่ยวกับ:

System.out.println("Good morning.");
System.out.println("Please enter your name");

เมื่อเทียบกับ

System.out.println("Good morning.\nPlease enter your name");

ตัวเลือกที่สองนั้นเร็วกว่ามาก ฉันจะแนะนำเกี่ยวกับ 2X เร็ว .... ทำไม?

เนื่องจาก 90% (ที่มีข้อผิดพลาดกว้าง) ของงานไม่เกี่ยวข้องกับการดัมพ์อักขระไปยังเอาต์พุต แต่จำเป็นต้องมีค่าใช้จ่ายในการรักษาความปลอดภัยของเอาต์พุตที่จะเขียนลงไป

การประสานข้อมูล

System.outPrintStreamเป็น การนำ Java ไปใช้ทั้งหมดที่ฉันรู้จัก PrintStream ภายใน: ดูรหัสบน GrepCode!.

สิ่งนี้หมายความว่าอะไรสำหรับรหัสของคุณ?

มันหมายความว่าทุกครั้งที่คุณโทร System.out.println(...)คุณกำลังซิงโครไนซ์โมเดลหน่วยความจำของคุณคุณกำลังตรวจสอบและรอการล็อก เธรดอื่น ๆ ที่เรียก System.out จะถูกล็อกเช่นกัน

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

ที่กรอกด้วยน้ำ

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

ตัวเลขบางตัว

ฉันรวบรวมการทดสอบเล็กน้อยต่อไปนี้:

public class ConsolePerf {

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            benchmark("Warm " + i);
        }
        benchmark("real");
    }

    private static void benchmark(String string) {
        benchString(string + "short", "This is a short String");
        benchString(string + "long", "This is a long String with a number of newlines\n"
                  + "in it, that should simulate\n"
                  + "printing some long sentences and log\n"
                  + "messages.");

    }

    private static final int REPS = 1000;

    private static void benchString(String name, String value) {
        long time = System.nanoTime();
        for (int i = 0; i < REPS; i++) {
            System.out.println(value);
        }
        double ms = (System.nanoTime() - time) / 1000000.0;
        System.err.printf("%s run in%n    %12.3fms%n    %12.3f lines per ms%n    %12.3f chars per ms%n",
                name, ms, REPS/ms, REPS * (value.length() + 1) / ms);

    }


}

รหัสค่อนข้างง่ายมันพิมพ์ซ้ำ ๆ สั้น ๆ หรือสตริงยาวออก สตริงที่ยาวมีการขึ้นบรรทัดใหม่หลายบรรทัด มันวัดระยะเวลาที่ใช้ในการพิมพ์ 1,000 รอบในแต่ละรอบ

ถ้าฉันเรียกใช้ที่พรอมต์คำสั่ง unix (Linux) และเปลี่ยนเส้นทางSTDOUTไปยัง/dev/nullและพิมพ์ผลลัพธ์จริงไปยังSTDERRฉันสามารถทำสิ่งต่อไปนี้:

java -cp . ConsolePerf > /dev/null 2> ../errlog

เอาต์พุต (เป็น errlog) ดูเหมือนว่า:

Warm 0short run in
           7.264ms
         137.667 lines per ms
        3166.345 chars per ms
Warm 0long run in
           1.661ms
         602.051 lines per ms
       74654.317 chars per ms
Warm 1short run in
           1.615ms
         619.327 lines per ms
       14244.511 chars per ms
Warm 1long run in
           2.524ms
         396.238 lines per ms
       49133.487 chars per ms
.......
Warm 99short run in
           1.159ms
         862.569 lines per ms
       19839.079 chars per ms
Warm 99long run in
           1.213ms
         824.393 lines per ms
      102224.706 chars per ms
realshort run in
           1.204ms
         830.520 lines per ms
       19101.959 chars per ms
reallong run in
           1.215ms
         823.160 lines per ms
      102071.811 chars per ms

สิ่งนี้หมายความว่า? ให้ฉันทำซ้ำ 'บทสุดท้าย':

realshort run in
           1.204ms
         830.520 lines per ms
       19101.959 chars per ms
reallong run in
           1.215ms
         823.160 lines per ms
      102071.811 chars per ms

นั่นหมายความว่าสำหรับทุกเจตนาและวัตถุประสงค์แม้ว่าบรรทัด 'long' จะยาวกว่าประมาณ 5 เท่าและมีการขึ้นบรรทัดใหม่หลายครั้ง แต่ก็ใช้เวลาในการส่งออกสั้นเป็นบรรทัดสั้น ๆ

จำนวนตัวอักษรต่อวินาทีสำหรับระยะยาวนั้นมากถึง 5 เท่าและเวลาที่ผ่านไปนั้นใกล้เคียงกัน .....

ในคำอื่น ๆ ผลงานของคุณที่สมดุลเมื่อเทียบกับจำนวนของ printlns คุณมีไม่สิ่งที่งานพิมพ์ที่คุณพิมพ์พิมพ์

อัปเดต:จะเกิดอะไรขึ้นหากคุณเปลี่ยนเส้นทางไปยังไฟล์แทนที่จะเป็น / dev / null

realshort run in
           2.592ms
         385.815 lines per ms
        8873.755 chars per ms
reallong run in
           2.686ms
         372.306 lines per ms
       46165.955 chars per ms

มันช้ากว่ามาก แต่สัดส่วนก็ใกล้เคียงกัน ....


เพิ่มตัวเลขประสิทธิภาพ
rolfl

คุณต้องพิจารณาปัญหาที่"\n"อาจไม่ใช่ตัวยุติการใช้สายที่ถูกต้อง printlnจะยุติบรรทัดโดยอัตโนมัติด้วยอักขระที่ถูกต้อง แต่การ\nใส่a ลงในสตริงของคุณโดยตรงอาจทำให้เกิดปัญหาได้ หากคุณต้องการที่จะทำมันขวาคุณอาจจำเป็นต้องใช้การจัดรูปแบบสตริงหรือline.separatorสถานที่ให้บริการระบบ printlnสะอาดกว่ามาก
user2357112 รองรับ Monica

3
นี่คือการวิเคราะห์ที่ยอดเยี่ยมทั้งหมดดังนั้น +1 อย่างแน่นอน แต่ฉันยืนยันว่าเมื่อคุณมุ่งมั่นที่จะส่งออกคอนโซลความแตกต่างเล็กน้อยของประสิทธิภาพเหล่านี้จะออกไปนอกหน้าต่าง หากอัลกอริทึมของโปรแกรมของคุณทำงานเร็วกว่าการแสดงผลลัพธ์ (ที่ระดับเล็ก ๆ ของผลลัพธ์) คุณสามารถพิมพ์อักขระแต่ละตัวทีละตัวและไม่สังเกตเห็นความแตกต่าง
David Harkness

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

6
สิ่งสำคัญคือต้องจำไว้ว่าไม่มีความเร็วใดที่สำคัญเลยเมื่อวางไว้ถัดจากฟังก์ชั่นที่รอการป้อนข้อมูลของผู้ใช้
vmrob

2

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

แต่มันก็ไม่ใช่ปัญหาเพราะคนส่วนใหญ่ไม่ได้ทำแบบนี้ เมื่อพวกเขาต้องการทำ IOs จำนวนมากพวกเขาใช้บัฟเฟอร์ (BufferedReader, BufferedWriter, ฯลฯ ) เมื่ออินพุตบัฟเฟอร์คุณจะเห็นว่าประสิทธิภาพคล้ายกันมากพอที่คุณไม่ต้องกังวลเกี่ยวกับการมี พวงprintlnหรือน้อยprintlnหรือไม่กี่

ดังนั้นเพื่อตอบคำถามเดิม ฉันจะบอกว่าไม่เลวถ้าคุณใช้printlnเพื่อพิมพ์บางสิ่งออกมาเป็นคนส่วนใหญ่จะใช้printlnสำหรับ


1

ในภาษาระดับสูงกว่าเช่น C และ C ++ นี่เป็นปัญหาที่น้อยกว่าใน Java

ก่อนอื่น C และ C ++ กำหนดการรวมสตริงสตริงเวลาคอมไพล์ดังนั้นคุณสามารถทำสิ่งต่อไปนี้:

std::cout << "Good morning everyone. I am here today to present you with a very, "
    "very lengthy sentence in order to prove a point about how it looks strange "
    "amongst other code.";

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

แม้ว่าจะมีความซับซ้อนเพิ่มขึ้นเล็กน้อยในคอมไพเลอร์และการนำไปใช้งาน แต่ C และ C ++ ก็ทำอะไรได้มากกว่านี้เพื่อซ่อนความซับซ้อนในการผลิตผลผลิตอย่างมีประสิทธิภาพจากโปรแกรมเมอร์ Java เป็นเหมือนภาษาแอสเซมบลีมากขึ้น - การเรียกแต่ละครั้งเพื่อSystem.out.printlnแปลโดยตรงไปยังการเรียกไปยังปฏิบัติการพื้นฐานเพื่อเขียนข้อมูลไปยังคอนโซล หากคุณต้องการกำหนดบัฟเฟอร์เพื่อปรับปรุงประสิทธิภาพต้องมีการจัดเตรียมแยกต่างหาก

ตัวอย่างเช่นนี่หมายความว่าใน C ++ เขียนใหม่ตัวอย่างก่อนหน้านี้กับสิ่งนี้:

std::cout << "Good morning everyone. I am here today to present you with a very, ";
std::cout << "very lengthy sentence in order to prove a point about how it looks ";       
std::cout << "strange amongst other code.";

... โดยปกติแล้ว1จะแทบไม่มีผลกระทบต่อประสิทธิภาพ การใช้แต่ละครั้งcoutจะฝากข้อมูลลงในบัฟเฟอร์ บัฟเฟอร์นั้นจะถูกล้างไปยังสตรีมพื้นฐานเมื่อบัฟเฟอร์เต็มหรือโค้ดพยายามอ่านอินพุตจากการใช้ (เช่น with std::cin)

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

sync_with_stdioยังควบคุมระดับการซิงโครไนซ์ระหว่างเธรด หากเปิดใช้งานการซิงโครไนซ์ (ค่าเริ่มต้น) ที่เขียนไปยัง iostream จากหลายเธรดสามารถส่งผลให้ข้อมูลจากเธรดถูกอินเตอร์ลีฟ แต่จะป้องกันสภาวะการแข่งขันใด ๆ IOW โปรแกรมของคุณจะทำงานและสร้างเอาต์พุต แต่ถ้ามีมากกว่าหนึ่งเธรดที่เขียนไปยังสตรีมในแต่ละครั้งการ intermixing โดยพลการของข้อมูลจากเธรดที่แตกต่างกันโดยทั่วไปแล้วจะทำให้เอาต์พุตไม่มีประโยชน์เลย

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

สรุป

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


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


13
" ในภาษาระดับสูงกว่าเช่น C และ C ++ นี่เป็นปัญหาน้อยกว่าใน Java " - อะไรนะ C และ C ++ เป็นภาษาระดับต่ำกว่า Java นอกจากนี้คุณลืมบรรทัดสิ้นสุดของคุณ
user2357112 รองรับ Monica

1
ตลอดฉันชี้ไปที่วัตถุประสงค์พื้นฐานสำหรับ Java เป็นภาษาระดับต่ำกว่า ไม่แน่ใจว่าคุณกำลังพูดถึงอะไร
Jerry Coffin

2
Java ยังทำการต่อเวลาคอมไพล์ด้วย ตัวอย่างเช่น"2^31 - 1 = " + Integer.MAX_VALUEถูกเก็บไว้เป็นสตริง interned เดียว (JLS Sec 3.10.5และ15.28 )
200_success

2
@ 200_success: Java กำลังทำการต่อสตริง ณ เวลารวบรวมดูเหมือนว่าจะลดลงมาที่§15.18.1: "วัตถุสตริงถูกสร้างขึ้นใหม่ (§12.5) เว้นแต่ว่านิพจน์นั้นเป็นนิพจน์คงที่ของเวลารวบรวม ((15.28)" สิ่งนี้ดูเหมือนจะอนุญาต แต่ไม่ต้องการให้มีการต่อข้อมูลในเวลารวบรวม คือผลลัพธ์จะต้องถูกสร้างขึ้นใหม่หากอินพุตไม่ใช่ค่าคงที่เวลาคอมไพล์ แต่ไม่มีข้อกำหนดใดที่ทำในทิศทางใดทิศทางหนึ่งหากเป็นค่าคงที่ของเวลาคอมไพล์ หากต้องการรวบรวมเวลาที่คอมไพล์คุณจะต้องอ่าน (โดยนัย) "ถ้า" เป็นความหมายที่แท้จริง "ถ้าและเพียงถ้า"
Jerry Coffin

2
@Phoshi: ลองกับแหล่งข้อมูลไม่ได้คล้ายคลึงกับ RAII RAII อนุญาตให้คลาสจัดการทรัพยากร แต่ลองใช้ทรัพยากรที่ต้องใช้รหัสไคลเอนต์เพื่อจัดการทรัพยากร ฟีเจอร์ (abstractions, แม่นยำยิ่งขึ้น) ที่หนึ่งมีและขาดอื่น ๆ ที่เกี่ยวข้องทั้งหมด - ในความเป็นจริงมันเป็นสิ่งที่ทำให้ภาษาหนึ่งในระดับที่สูงกว่าอีกภาษาหนึ่ง
Jerry Coffin

1

ในขณะที่ประสิทธิภาพไม่ได้เป็นปัญหาที่นี่ความสามารถในการอ่านprintlnประโยคที่ไม่ดีชี้ไปที่การออกแบบที่ขาดหายไป

ทำไมเราถึงเขียนลำดับของprintlnข้อความมากมาย? ถ้ามันเป็นบล็อกข้อความคงที่เพียงบล็อกเดียวเช่น--helpข้อความในคำสั่งคอนโซลมันจะดีกว่ามากถ้าให้มันเป็น ressource แยกต่างหากและอ่านมันและเขียนมันลงบนหน้าจอตามที่ร้องขอ

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

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

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