Java NIO FileChannel เทียบกับประสิทธิภาพและประโยชน์ของ FileOutputstream


169

ฉันพยายามที่จะคิดออกว่ามีความแตกต่างในประสิทธิภาพ (หรือข้อดี) เมื่อเราใช้ nio FileChannelกับปกติFileInputStream/FileOuputStreamเพื่ออ่านและเขียนไฟล์ไปยังระบบไฟล์ ฉันสังเกตว่าบนเครื่องของฉันทั้งสองทำงานที่ระดับเดียวกันหลายครั้งFileChannelด้วยวิธีที่ช้ากว่า ฉันขอรายละเอียดเพิ่มเติมเปรียบเทียบสองวิธีนี้ได้ไหม 350MBนี่คือรหัสที่ผมใช้ไฟล์ที่ฉันกำลังทดสอบกับประมาณ เป็นตัวเลือกที่ดีในการใช้คลาสที่ใช้ NIO สำหรับ File I / O หรือไม่หากฉันไม่ได้ดูที่การเข้าถึงแบบสุ่มหรือคุณสมบัติขั้นสูงอื่น ๆ

package trialjavaprograms;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class JavaNIOTest {
    public static void main(String[] args) throws Exception {
        useNormalIO();
        useFileChannel();
    }

    private static void useNormalIO() throws Exception {
        File file = new File("/home/developer/test.iso");
        File oFile = new File("/home/developer/test2");

        long time1 = System.currentTimeMillis();
        InputStream is = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(oFile);
        byte[] buf = new byte[64 * 1024];
        int len = 0;
        while((len = is.read(buf)) != -1) {
            fos.write(buf, 0, len);
        }
        fos.flush();
        fos.close();
        is.close();
        long time2 = System.currentTimeMillis();
        System.out.println("Time taken: "+(time2-time1)+" ms");
    }

    private static void useFileChannel() throws Exception {
        File file = new File("/home/developer/test.iso");
        File oFile = new File("/home/developer/test2");

        long time1 = System.currentTimeMillis();
        FileInputStream is = new FileInputStream(file);
        FileOutputStream fos = new FileOutputStream(oFile);
        FileChannel f = is.getChannel();
        FileChannel f2 = fos.getChannel();

        ByteBuffer buf = ByteBuffer.allocateDirect(64 * 1024);
        long len = 0;
        while((len = f.read(buf)) != -1) {
            buf.flip();
            f2.write(buf);
            buf.clear();
        }

        f2.close();
        f.close();

        long time2 = System.currentTimeMillis();
        System.out.println("Time taken: "+(time2-time1)+" ms");
    }
}

5
transferTo/ transferFromจะธรรมดากว่าสำหรับการคัดลอกไฟล์ เทคนิคใดก็ตามไม่ควรทำให้ฮาร์ดไดรฟ์ของคุณเร็วขึ้นหรือช้าลง แต่ฉันคิดว่าอาจมีปัญหาหากอ่านชิ้นเล็ก ๆ ในแต่ละครั้งและทำให้หัวใช้เวลาในการค้นหาน้อยเกินไป
Tom Hawtin - tackline

1
(คุณไม่ได้พูดถึงระบบปฏิบัติการที่คุณใช้หรือผู้จำหน่ายและรุ่นของ JRE)
Tom Hawtin - tackline

โอ๊ะโอฉันใช้ FC10 กับ Sun JDK6 แล้ว
Keshav

คำตอบ:


202

ประสบการณ์ของผมที่มีขนาดไฟล์ขนาดใหญ่ได้รับการที่จะเร็วกว่า java.nio เร็วขึ้นอย่างสมบูรณ์ ชอบในช่วง> 250% ที่กล่าวว่าฉันกำลังกำจัดคอขวดที่เห็นได้ชัดซึ่งฉันขอแนะนำให้ใช้มาตรฐานไมโครของคุณอาจประสบ พื้นที่ที่มีศักยภาพสำหรับการตรวจสอบ:java.io

ขนาดบัฟเฟอร์ อัลกอริทึมที่คุณมีคือ

  • คัดลอกจากดิสก์ไปยังบัฟเฟอร์
  • คัดลอกจากบัฟเฟอร์ไปยังดิสก์

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

อย่าทำการเปรียบเทียบ java ที่อ่านและเขียนลงในดิสก์เดียวกัน

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

อย่าใช้บัฟเฟอร์หากคุณไม่ต้องการ

ทำไมคัดลอกไปยังหน่วยความจำหากเป้าหมายของคุณคือดิสก์อื่นหรือ NIC ด้วยไฟล์ที่มีขนาดใหญ่ขึ้นความหน่วงแฝงจึงไม่สำคัญ

เหมือนที่คนอื่นพูด, ใช้FileChannel.transferTo()หรือFileChannel.transferFrom(). ข้อได้เปรียบที่สำคัญคือ JVM ใช้การเข้าถึงระบบปฏิบัติการของ DMA ( การเข้าถึงหน่วยความจำโดยตรง ) หากมี (สิ่งนี้ขึ้นอยู่กับการใช้งาน แต่รุ่น Sun และ IBM รุ่นใหม่โดยทั่วไป CPU ใช้งานได้ดี) สิ่งที่เกิดขึ้นคือข้อมูลตรงไปยัง / จากดิสก์ไปยังบัสและจากนั้นไปยังปลายทาง ... ผ่านวงจรใด ๆ ผ่าน RAM หรือ CPU

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

ใช้ข้อมูลการผลิตและสภาพแวดล้อม

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

มาตรฐานของฉันแข็งแกร่งและเชื่อถือได้เพราะพวกเขาเกิดขึ้นในระบบการผลิต, ระบบอ้วน, ระบบภายใต้การโหลด, รวบรวมในบันทึก ไม่ใช่ไดรฟ์ SATA ขนาด 7200 RPM 2.5 "ในโน้ตบุ๊คของฉันในขณะที่ฉันดูอย่างเข้มข้นเนื่องจาก JVM ทำงานบนฮาร์ดดิสก์ของฉัน

คุณกำลังทำอะไรอยู่ มันเป็นเรื่องสำคัญ


@Stu Thompson - ขอบคุณสำหรับการโพสต์ของคุณ ฉันเจอคำตอบของคุณตั้งแต่ฉันค้นคว้าในหัวข้อเดียวกัน ฉันพยายามที่จะเข้าใจการปรับปรุงระบบปฏิบัติการที่ nio แสดงถึงโปรแกรมเมอร์ Java คู่ของพวกเขาเป็น - DMA และไฟล์ที่แมปหน่วยความจำ คุณเคยเจอกับการปรับปรุงที่มากกว่านี้ไหม? PS - ลิงก์บล็อกของคุณเสีย
Andy Dufresne

@AndyDufresne บล็อกของฉันไม่ทำงานในขณะนี้จะมีขึ้นในสัปดาห์นี้ - อยู่ระหว่างการย้ายบล็อก
Stu Thompson

15
: ที่นี่การเชื่อมโยงบล็อกใน archive.org มี web.archive.org/web/20120815094827/http://geekomatic.ch/2008/... web.archive.org/web/20120821114802/http://geekomatic.ch/2009 / …
Arthur Edelstein

1
วิธีการเพียงแค่คัดลอกไฟล์จากไดเรกทอรีหนึ่งไปยังอีก? (ดิสก์ไดรฟ์แต่ละตัวที่แตกต่างกัน)
Deckard


38

หากสิ่งที่คุณต้องการเปรียบเทียบคือประสิทธิภาพของการคัดลอกไฟล์ดังนั้นสำหรับการทดสอบแชนเนลคุณควรทำดังนี้:

final FileInputStream inputStream = new FileInputStream(src);
final FileOutputStream outputStream = new FileOutputStream(dest);
final FileChannel inChannel = inputStream.getChannel();
final FileChannel outChannel = outputStream.getChannel();
inChannel.transferTo(0, inChannel.size(), outChannel);
inChannel.close();
outChannel.close();
inputStream.close();
outputStream.close();

สิ่งนี้จะไม่ช้ากว่าการบัฟเฟอร์ตัวเองจากช่องหนึ่งไปอีกช่องหนึ่งและอาจจะเร็วขึ้นอย่างมาก อ้างอิงจาก Javadocs:

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


7

จากการทดสอบของฉัน (Win7 64 บิต, 6GB RAM, Java6) การถ่ายโอน NIO ทำได้อย่างรวดเร็วจากไฟล์ขนาดเล็กและช้าลงสำหรับไฟล์ขนาดใหญ่ NIO databuffer flip มีประสิทธิภาพเหนือกว่ามาตรฐาน IO เสมอ

  • คัดลอก 1,000x2MB

    1. NIO (ถ่ายโอนจาก) ~ 2300ms
    2. NIO (เปิดฐานข้อมูลโดยตรง 5000b พลิก) ~ 3500ms
    3. มาตรฐาน IO (บัฟเฟอร์ 5000b) ~ 6000ms
  • คัดลอก 100x20mb

    1. NIO (พลิกฐานข้อมูลโดยตรง 5000b) ~ 4000ms
    2. NIO (ถ่ายโอนจาก) ~ 5000ms
    3. มาตรฐาน IO (บัฟเฟอร์ 5000b) ~ 6500ms
  • กำลังคัดลอก 1x1000mb

    1. NIO (พลิกฐานข้อมูลโดยตรง 5000b) ~ 4500 วินาที
    2. มาตรฐาน IO (บัฟเฟอร์ 5000b) ~ 7000ms
    3. NIO (ถ่ายโอนจาก) ~ 8000ms

เมธอด transferTo () ทำงานกับไฟล์จำนวนหนึ่ง ไม่ได้มีไว้สำหรับวิธีการคัดลอกไฟล์ระดับสูง: วิธีคัดลอกไฟล์ขนาดใหญ่ใน Windows XP ได้อย่างไร


6

ตอบคำถาม "ส่วนที่มีประโยชน์" ของคำถาม:

หนึ่ง gotcha ค่อนข้างบอบบางของการใช้FileChannelมากกว่าFileOutputStreamก็คือว่าการดำเนินการใด ๆ ของการดำเนินการปิดกั้น (เช่นread()หรือwrite()) จากหัวข้อที่ในรัฐขัดจังหวะจะทำให้เกิดช่องทางที่จะใกล้อย่างฉับพลันjava.nio.channels.ClosedByInterruptExceptionจะทำให้เกิดช่องทางที่จะใกล้อย่างฉับพลัน

ตอนนี้อาจเป็นสิ่งที่ดีถ้าสิ่งที่ FileChannelใช้เป็นส่วนหนึ่งของฟังก์ชั่นหลักของเธรดและการออกแบบคำนึงถึงสิ่งนี้

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

โชคไม่ดีที่นี่มีความละเอียดอ่อนเพราะการไม่คิดบัญชีสามารถนำไปสู่ข้อบกพร่องที่ส่งผลกระทบต่อความสมบูรณ์ของการเขียน [1] [2]


3

ฉันทดสอบประสิทธิภาพของ FileInputStream vs. FileChannel เพื่อถอดรหัสไฟล์ที่เข้ารหัส base64 ในประสบการณ์ของฉันฉันได้ทดสอบไฟล์ขนาดใหญ่และ io แบบดั้งเดิมนั้นเร็วกว่า nio เล็กน้อย

FileChannel อาจมีข้อได้เปรียบในเวอร์ชันก่อนหน้าของ jvm เนื่องจากค่าใช้จ่ายในการซิงโครไนซ์ในคลาสที่เกี่ยวข้องกับ io แต่ jvm ที่ทันสมัยค่อนข้างดีในการลบล็อคที่ไม่จำเป็นออกไป


2

หากคุณไม่ได้ใช้คุณสมบัติ transferTo หรือคุณสมบัติที่ไม่ปิดกั้นคุณจะไม่สังเกตเห็นความแตกต่างระหว่าง IO แบบดั้งเดิมและ NIO (2) เนื่องจาก IO แผนที่ดั้งเดิมไปยัง NIO

แต่ถ้าคุณสามารถใช้คุณสมบัติของ NIO เช่น transferFrom / To หรือต้องการใช้ Buffers แน่นอนว่า NIO เป็นวิธีที่จะไป


0

ประสบการณ์ของฉันคือ NIO เร็วขึ้นด้วยไฟล์ขนาดเล็ก แต่เมื่อพูดถึงไฟล์ขนาดใหญ่ FileInputStream / FileOutputStream นั้นเร็วกว่ามาก


4
คุณได้รับการผสมที่? ประสบการณ์ของฉันคือไฟล์ที่ใหญ่กว่าjava.nioเร็วกว่าไม่เล็กกว่า java.io
Stu Thompson

ไม่ประสบการณ์ของฉันคืออีกด้านหนึ่ง java.nioเร็วพอที่ไฟล์จะเล็กพอที่จะแมปกับหน่วยความจำ ถ้ามันใหญ่ขึ้น (200 MB ขึ้นไป) java.ioเร็วกว่า
tangens

ว้าว. ตรงกันข้ามทั้งหมดของฉัน โปรดทราบว่าคุณไม่จำเป็นต้อง map ไฟล์เพื่ออ่านมัน - FileChannel.read()หนึ่งสามารถอ่านจาก java.nioมีไม่ได้เป็นเพียงวิธีการหนึ่งเดียวในการอ่านไฟล์โดยใช้
Stu Thompson

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