การโทรแบบคงที่ของ Java มีราคาแพงกว่าหรือน้อยกว่าการโทรแบบไม่คง


คำตอบ:


74

อันดับแรก: คุณไม่ควรเลือกแบบคงที่และไม่คงที่โดยพิจารณาจากประสิทธิภาพ

ประการที่สอง: ในทางปฏิบัติมันจะไม่สร้างความแตกต่างใด ๆ Hotspot อาจเลือกที่จะเพิ่มประสิทธิภาพในรูปแบบที่ทำให้การโทรแบบคงที่เร็วขึ้นสำหรับวิธีการหนึ่งการโทรแบบไม่คงที่เร็วขึ้นสำหรับอีกวิธีหนึ่ง

ประการที่สาม: Mythos ส่วนใหญ่ที่อยู่รอบ ๆ แบบคงที่และไม่คงที่นั้นขึ้นอยู่กับ JVM ที่เก่ามาก (ซึ่งไม่ได้ทำที่ใดก็ได้ใกล้กับการเพิ่มประสิทธิภาพที่ Hotspot ทำ) หรือบางเรื่องที่จำได้เกี่ยวกับ C ++ (ซึ่งการเรียกแบบไดนามิกจะใช้การเข้าถึงหน่วยความจำอีกหนึ่งครั้งกว่าการโทรแบบคงที่)


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

2
@AaronDigulla -.- จะเป็นอย่างไรถ้าฉันบอกคุณว่าฉันมาที่นี่เพราะฉันกำลังเพิ่มประสิทธิภาพตอนนี้ไม่ใช่ก่อนเวลาอันควร แต่เมื่อฉันต้องการจริงๆล่ะ? คุณคิดว่า OP ต้องการเพิ่มประสิทธิภาพก่อนเวลาอันควร แต่คุณรู้ว่าไซต์นี้เป็นเว็บไซต์ระดับโลก ... ใช่ไหม? ฉันไม่อยากพูดหยาบคาย แต่โปรดอย่าคิดแบบนี้ในครั้งต่อไป
ดาลิบอร์ฟิลัส

1
@DaliborFilus ฉันต้องหาจุดสมดุล การใช้วิธีคงที่ทำให้เกิดปัญหาทุกประเภทดังนั้นจึงควรหลีกเลี่ยงโดยเฉพาะอย่างยิ่งเมื่อคุณไม่รู้ว่าคุณกำลังทำอะไรอยู่ ประการที่สองโค้ดที่ "ช้า" ส่วนใหญ่เป็นเพราะการออกแบบ (ไม่ดี) ไม่ใช่เพราะ Language-of-Choice นั้นช้า หากโค้ดของคุณทำงานช้าวิธีการแบบคงที่อาจไม่สามารถบันทึกได้เว้นแต่วิธีการโทรที่ไม่ทำอะไรเลย ในกรณีส่วนใหญ่รหัสในวิธีการจะทำให้ค่าใช้จ่ายในการโทรลดลง
Aaron Digulla

7
โหวตลดลง สิ่งนี้ไม่ตอบคำถาม คำถามถามเกี่ยวกับประโยชน์ด้านประสิทธิภาพ ไม่ได้ขอความคิดเห็นเกี่ยวกับหลักการออกแบบ
Colm Bhandal

4
ถ้าฉันฝึกนกแก้วให้พูดว่า "การสบประมาทก่อนวัยอันควรเป็นรากเหง้าของความชั่วร้ายทั้งหมด" ฉันจะได้รับคะแนนโหวต 1,000 คะแนนจากผู้ที่รู้เรื่องการแสดงมากพอ ๆ กับนกแก้ว
rghome

64

สี่ปีต่อมา ...

เอาล่ะด้วยความหวังที่จะตั้งคำถามนี้ครั้งแล้วครั้งเล่าฉันได้เขียนเกณฑ์มาตรฐานซึ่งแสดงให้เห็นว่าการโทรประเภทต่างๆ (เสมือนไม่ใช่เสมือนแบบคงที่) เปรียบเทียบกันอย่างไร

ฉันวิ่งบน ideoneและนี่คือสิ่งที่ฉันได้รับ:

(การทำซ้ำจำนวนมากจะดีกว่า)

    Success time: 3.12 memory: 320576 signal:0
  Name          |  Iterations
    VirtualTest |  128009996
 NonVirtualTest |  301765679
     StaticTest |  352298601
Done.

ตามที่คาดไว้การเรียกใช้เมธอดเสมือนเป็นการเรียกใช้เมธอดที่ช้าที่สุดและไม่ใช่วิธีเสมือนจะเร็วกว่าและการเรียกเมธอดแบบคงที่จะเร็วกว่า

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

ฉันคิดว่าผลลัพธ์จะแตกต่างกันไปในแต่ละ CPU และจาก JVM ถึง JVM ดังนั้นลองดูสิว่าคุณจะได้อะไร:

import java.io.*;

class StaticVsInstanceBenchmark
{
    public static void main( String[] args ) throws Exception
    {
        StaticVsInstanceBenchmark program = new StaticVsInstanceBenchmark();
        program.run();
    }

    static final int DURATION = 1000;

    public void run() throws Exception
    {
        doBenchmark( new VirtualTest( new ClassWithVirtualMethod() ), 
                     new NonVirtualTest( new ClassWithNonVirtualMethod() ), 
                     new StaticTest() );
    }

    void doBenchmark( Test... tests ) throws Exception
    {
        System.out.println( "  Name          |  Iterations" );
        doBenchmark2( devNull, 1, tests ); //warmup
        doBenchmark2( System.out, DURATION, tests );
        System.out.println( "Done." );
    }

    void doBenchmark2( PrintStream printStream, int duration, Test[] tests ) throws Exception
    {
        for( Test test : tests )
        {
            long iterations = runTest( duration, test );
            printStream.printf( "%15s | %10d\n", test.getClass().getSimpleName(), iterations );
        }
    }

    long runTest( int duration, Test test ) throws Exception
    {
        test.terminate = false;
        test.count = 0;
        Thread thread = new Thread( test );
        thread.start();
        Thread.sleep( duration );
        test.terminate = true;
        thread.join();
        return test.count;
    }

    static abstract class Test implements Runnable
    {
        boolean terminate = false;
        long count = 0;
    }

    static class ClassWithStaticStuff
    {
        static int staticDummy;
        static void staticMethod() { staticDummy++; }
    }

    static class StaticTest extends Test
    {
        @Override
        public void run()
        {
            for( count = 0;  !terminate;  count++ )
            {
                ClassWithStaticStuff.staticMethod();
            }
        }
    }

    static class ClassWithVirtualMethod implements Runnable
    {
        int instanceDummy;
        @Override public void run() { instanceDummy++; }
    }

    static class VirtualTest extends Test
    {
        final Runnable runnable;

        VirtualTest( Runnable runnable )
        {
            this.runnable = runnable;
        }

        @Override
        public void run()
        {
            for( count = 0;  !terminate;  count++ )
            {
                runnable.run();
            }
        }
    }

    static class ClassWithNonVirtualMethod
    {
        int instanceDummy;
        final void nonVirtualMethod() { instanceDummy++; }
    }

    static class NonVirtualTest extends Test
    {
        final ClassWithNonVirtualMethod objectWithNonVirtualMethod;

        NonVirtualTest( ClassWithNonVirtualMethod objectWithNonVirtualMethod )
        {
            this.objectWithNonVirtualMethod = objectWithNonVirtualMethod;
        }

        @Override
        public void run()
        {
            for( count = 0;  !terminate;  count++ )
            {
                objectWithNonVirtualMethod.nonVirtualMethod();
            }
        }
    }

    static final PrintStream devNull = new PrintStream( new OutputStream() 
    {
        public void write(int b) {}
    } );
}

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

นอกจากนี้ยังมีการเรียกวิธีการเสมือนด้วยเหตุผล; พวกเขามีจุดประสงค์เพื่อให้บริการและถูกนำไปใช้โดยใช้วิธีการที่มีประสิทธิภาพสูงสุดที่จัดหาโดยฮาร์ดแวร์พื้นฐาน (ชุดคำสั่ง CPU) หากคุณต้องการกำจัดโดยการแทนที่ด้วยการเรียกที่ไม่ใช่เสมือนหรือแบบคงที่คุณจะต้องเพิ่มรหัสพิเศษมากถึง iota เพื่อจำลองฟังก์ชันการทำงานของพวกเขาค่าใช้จ่ายสุทธิที่เกิดขึ้นของคุณจะถูกผูกไว้ ไม่น้อย แต่มากขึ้น อาจเป็นไปได้มากมากมากอย่างไม่น่าเชื่อมากขึ้น


7
'Virtual' คือคำศัพท์ C ++ ไม่มีเมธอดเสมือนใน Java มีวิธีการทั่วไปซึ่งเป็น runtime-polymorphic และวิธีการคงที่หรือขั้นสุดท้ายซึ่งไม่ใช่
Zhenya

18
@levgen ใช่สำหรับคนที่มีมุมมองแคบพอ ๆ กับภาพรวมระดับสูงอย่างเป็นทางการของภาษามันก็เป็นอย่างที่คุณพูด แต่แน่นอนว่าแนวคิดระดับสูงนั้นถูกนำไปใช้โดยใช้กลไกระดับต่ำที่ได้รับการยอมรับมาเป็นอย่างดีซึ่งถูกคิดค้นขึ้นมาเป็นเวลานานก่อนที่จาวาจะเกิดขึ้นและวิธีการเสมือนก็เป็นหนึ่งในนั้น หากคุณมองเพียงเล็กน้อยภายใต้ประทุนคุณจะเห็นได้ทันทีว่าเป็นเช่นนั้น: docs.oracle.com/javase/specs/jvms/se7/html/…
Mike

14
ขอขอบคุณที่ตอบคำถามโดยไม่ตั้งข้อสันนิษฐานเกี่ยวกับการเพิ่มประสิทธิภาพก่อนกำหนด คำตอบที่ดี
vegemite4me

3
ใช่นั่นคือสิ่งที่ฉันหมายถึง อย่างไรก็ตามฉันเพิ่งทำการทดสอบบนเครื่องของฉัน นอกเหนือจากความกระวนกระวายใจที่คุณคาดหวังได้จากเกณฑ์มาตรฐานดังกล่าวแล้วยังไม่มีความแตกต่างในด้านความเร็วใด ๆ : VirtualTest | 488846733 -- NonVirtualTest | 480530022 -- StaticTest | 484353198ในการติดตั้ง OpenJDK ของฉัน FTR: นั่นเป็นความจริงถ้าฉันลบfinalตัวปรับแต่ง Btw. ฉันต้องสร้างterminateสนาม volatileมิฉะนั้นการทดสอบจะไม่เสร็จสิ้น
Marten

4
FYI, ฉันได้รับผลลัพธ์ที่ค่อนข้างน่าแปลกใจใน Nexus 5 ที่ใช้ Android ที่ VirtualTest | 12451872 -- NonVirtualTest | 12089542 -- StaticTest | 81811706: ไม่เพียง แต่ OpenJDK บนโน้ตบุ๊กของฉันเท่านั้นที่สามารถทำซ้ำได้มากขึ้น 40 เท่า แต่การทดสอบแบบคงที่ยังมีปริมาณงานน้อยลงประมาณ 30% นี่อาจเป็นปรากฏการณ์เฉพาะของ ART เพราะฉันได้รับผลลัพธ์ที่คาดหวังบนแท็บเล็ต Android 4.4:VirtualTest | 138183740 -- NonVirtualTest | 142268636 -- StaticTest | 161388933
Marten

45

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

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


นี่เป็นสาเหตุที่เป็นไปได้ว่าทำไมการโทรแบบคงที่อาจเร็วกว่าคุณช่วยอธิบายเหตุผลเหล่านั้นให้ฉันฟังได้ไหม
JavaTechnical

6
@JavaTechnical: คำตอบอธิบายเหตุผลเหล่านั้น - ไม่มีการลบล้าง (ซึ่งหมายความว่าคุณไม่จำเป็นต้องดำเนินการเพื่อใช้งานในแต่ละครั้งและคุณสามารถอินไลน์ได้) และคุณไม่จำเป็นต้องตรวจสอบว่าคุณกำลังเรียกใช้เมธอดบน a การอ้างอิงว่าง
Jon Skeet

6
@JavaTechnical: ฉันไม่เข้าใจ ฉันเพิ่งให้สิ่งที่ไม่จำเป็นต้องคำนวณ / ตรวจสอบวิธีการคงที่พร้อมกับโอกาสในการแทรก การไม่ทำงานเป็นผลประโยชน์ทางประสิทธิภาพ ยังเหลืออะไรให้เข้าใจอีก?
Jon Skeet

ตัวแปรคงที่เรียกค้นได้เร็วกว่าตัวแปรไม่คงที่หรือไม่?
JavaTechnical

1
@JavaTechnical: ไม่มีการตรวจสอบความว่างเปล่าที่จะดำเนินการ - แต่ถ้าคอมไพเลอร์ JIT สามารถลบการตรวจสอบนั้นได้ (ซึ่งจะเป็นแบบเฉพาะบริบท) ฉันจะไม่คาดหวังความแตกต่างมากนัก สิ่งต่างๆเช่นว่าหน่วยความจำอยู่ในแคชหรือไม่จะสำคัญกว่ามาก
Jon Skeet

18

เป็นคอมไพเลอร์ / VM เฉพาะ

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

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

อย่างไรก็ตามฉันได้เห็นว่าการเพิ่มประสิทธิภาพนี้ทำให้ประสิทธิภาพเพิ่มขึ้นอย่างมากในสถานการณ์ต่อไปนี้:

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

หากข้อมูลข้างต้นตรงกับคุณการทดสอบอาจคุ้มค่า

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


13

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



คุณช่วยอธิบายเพิ่มเติมได้ไหมว่า "" การเพิ่มประสิทธิภาพก่อนวัยอันควรเป็นรากเหง้าของความชั่วร้ายทั้งหมด "คืออะไร?
user2121

คำถามคือ "ประสิทธิภาพมีประโยชน์ไม่ทางใดก็ทางหนึ่งหรือไม่" และนี่ก็ตอบคำถามนั้นได้อย่างแท้จริง
DJClayworth

13

ดังที่ผู้โพสต์ก่อนหน้านี้ได้กล่าวไว้: ดูเหมือนว่าจะเป็นการเพิ่มประสิทธิภาพก่อนเวลาอันควร

อย่างไรก็ตามมีความแตกต่างอย่างหนึ่ง (ส่วนหนึ่งจากข้อเท็จจริงที่ว่าการเรียกใช้แบบไม่คงที่จำเป็นต้องมีการกด callee-object เพิ่มเติมไปยังสแต็กตัวถูกดำเนินการ):

เนื่องจากวิธีการแบบคงที่ไม่สามารถแทนที่ได้จะไม่มีการค้นหาเสมือนใด ๆในรันไทม์สำหรับการเรียกใช้เมธอดแบบคงที่ ซึ่งอาจส่งผลให้เกิดความแตกต่างที่สังเกตได้ภายใต้สถานการณ์บางอย่าง

ความแตกต่างในระดับไบต์รหัสคือที่เรียกวิธีไม่คงที่จะกระทำผ่านINVOKEVIRTUAL,INVOKEINTERFACEหรือในขณะที่เรียกวิธีการแบบคงที่จะกระทำผ่านINVOKESPECIALINVOKESTATIC


2
อย่างไรก็ตามวิธีการอินสแตนซ์ส่วนตัวคือ (อย่างน้อยโดยทั่วไป) ถูกเรียกใช้invokespecialเนื่องจากไม่ใช่เสมือน
Mark Peters

อาน่าสนใจฉันคิดได้แค่ตัวสร้างนั่นคือเหตุผลที่ฉันมองข้ามมันไป! ขอบคุณ! (อัปเดตคำตอบ)
aioobe

2
JVM จะปรับให้เหมาะสมหากมีการสร้างอินสแตนซ์ประเภทหนึ่ง ถ้า B ขยาย A และไม่มีอินสแตนซ์ของ B ถูกสร้างอินสแตนซ์การเรียกใช้เมธอดบน A จะไม่ต้องการการค้นหาตารางเสมือน
Steve Kuo

13

7 ปีต่อมา ...

ฉันไม่มีความมั่นใจอย่างมากในผลลัพธ์ที่ Mike Nakis พบเพราะพวกเขาไม่ได้จัดการกับปัญหาทั่วไปบางอย่างที่เกี่ยวข้องกับการเพิ่มประสิทธิภาพ Hotspot ฉันได้ทำการวัดประสิทธิภาพโดยใช้ JMH และพบว่าค่าใช้จ่ายของวิธีอินสแตนซ์อยู่ที่ประมาณ 0.75% บนเครื่องของฉันเทียบกับการโทรแบบคงที่ เนื่องจากค่าใช้จ่ายที่ต่ำฉันคิดว่ายกเว้นในการดำเนินการที่มีความอ่อนไหวต่อเวลาแฝงมากที่สุดจึงไม่ใช่ประเด็นที่น่ากังวลที่สุดในการออกแบบแอปพลิเคชัน ผลสรุปจากเกณฑ์มาตรฐาน JMH ของฉันมีดังนี้

java -jar target/benchmark.jar

# -- snip --

Benchmark                        Mode  Cnt          Score         Error  Units
MyBenchmark.testInstanceMethod  thrpt  200  414036562.933 ± 2198178.163  ops/s
MyBenchmark.testStaticMethod    thrpt  200  417194553.496 ± 1055872.594  ops/s

คุณสามารถดูรหัสได้ที่นี่ใน Github

https://github.com/nfisher/svsi

เกณฑ์มาตรฐานนั้นค่อนข้างเรียบง่าย แต่มีจุดมุ่งหมายเพื่อลดการกำจัดโค้ดที่ตายแล้วและการพับคงที่ อาจมีการเพิ่มประสิทธิภาพอื่น ๆ ที่ฉันพลาด / มองข้ามไปและผลลัพธ์เหล่านี้อาจแตกต่างกันไปตามรุ่น JVM และ OS

package ca.junctionbox.svsi;

import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.Blackhole;

class InstanceSum {
    public int sum(final int a, final int b) {
        return a + b;
    }
}

class StaticSum {
    public static int sum(final int a, final int b) {
        return a + b;
    }
}

public class MyBenchmark {
    private static final InstanceSum impl = new InstanceSum();

    @State(Scope.Thread)
    public static class Input {
        public int a = 1;
        public int b = 2;
    }

    @Benchmark
    public void testStaticMethod(Input i, Blackhole blackhole) {
        int sum = StaticSum.sum(i.a, i.b);
        blackhole.consume(sum);
    }

    @Benchmark
    public void testInstanceMethod(Input i, Blackhole blackhole) {
        int sum = impl.sum(i.a, i.b);
        blackhole.consume(sum);
    }
}

1
ความสนใจทางวิชาการล้วนๆที่นี่ ฉันอยากรู้เกี่ยวกับประโยชน์ที่เป็นไปได้ของการเพิ่มประสิทธิภาพไมโครประเภทนี้อาจมีในเมตริกอื่น ๆ นอกเหนือจากops/sสภาพแวดล้อม ART เป็นหลัก (เช่นการใช้หน่วยความจำการลดขนาดไฟล์. oat เป็นต้น) คุณรู้หรือไม่ว่ามีเครื่องมือ / วิธีง่ายๆที่สามารถใช้ในการเปรียบเทียบเมตริกอื่น ๆ เหล่านี้ได้หรือไม่?
Ryan Thomas

Hotspot พบว่าไม่มีส่วนขยายของ InstanceSum ใน classpath ลองเพิ่มคลาสอื่นที่ขยาย InstanceSum และแทนที่เมธอด
มิลาน

12

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

1. ) วิธีการแบบคงที่ไม่ใช่ความหลากหลายดังนั้น JVM จึงมีการตัดสินใจน้อยกว่าในการค้นหารหัสจริงเพื่อดำเนินการ นี่เป็นจุดที่สงสัยใน Age of Hotspot เนื่องจาก Hotspot จะเพิ่มประสิทธิภาพการเรียกวิธีการอินสแตนซ์ที่มีไซต์การใช้งานเพียงไซต์เดียวดังนั้นจึงจะดำเนินการเหมือนกัน

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


5

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

นี้เป็นส่วนใหญ่แน่นอนส่วนหนึ่งของ97% ของประสิทธิภาพการขนาดเล็กที่คุณควรลืมเกี่ยวกับ


2
ไม่ถูกต้อง. คุณไม่สามารถสันนิษฐานอะไรได้ อาจเป็นห่วงที่จำเป็นสำหรับ UI ส่วนหน้าซึ่งอาจสร้างความแตกต่างอย่างมากว่า UI เป็นอย่างไร ตัวอย่างเช่นการค้นหาในTableViewบันทึกหลายล้านรายการ
ไตรภาค

0

ในทางทฤษฎีราคาไม่แพง

การเริ่มต้นแบบคงที่จะทำได้แม้ว่าคุณจะสร้างอินสแตนซ์ของวัตถุในขณะที่วิธีการแบบคงที่จะไม่ทำการเริ่มต้นใด ๆ ตามปกติในตัวสร้าง

อย่างไรก็ตามฉันยังไม่ได้ทดสอบ


1
@ ร. Bemrose การเริ่มต้นแบบคงที่เกี่ยวข้องกับคำถามนี้อย่างไร?
Kirk Woll

@ Kirk Woll: เนื่องจาก Static Initialization เสร็จสิ้นในครั้งแรกที่มีการอ้างถึงคลาส ... รวมถึงก่อนการเรียกเมธอดแบบคงที่ครั้งแรก
Powerlord

@ ร. Bemrose แน่นอนเช่นเดียวกับการโหลดคลาสลงใน VM เพื่อเริ่มต้นด้วย ดูเหมือนว่าจะไม่ใช่ IMO
Kirk Woll

0

ดังที่ Jon ตั้งข้อสังเกตว่าวิธีการแบบคงที่ไม่สามารถแทนที่ได้ดังนั้นการเรียกใช้วิธีการแบบคงที่อาจเป็น - บนรันไทม์ Java ที่ไร้เดียงสาเพียงพอ - เร็วกว่าการเรียกใช้วิธีอินสแตนซ์

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


-2

ฉันต้องการเพิ่มคำตอบที่ดีอื่น ๆ ที่นี่ซึ่งขึ้นอยู่กับขั้นตอนของคุณเช่น:

Public class MyDao {

   private String sql = "select * from MY_ITEM";

   public List<MyItem> getAllItems() {
       springJdbcTemplate.query(sql, new MyRowMapper());
   };
};

ให้ความสนใจว่าคุณสร้างวัตถุ MyRowMapper ใหม่ต่อการโทรแต่ละครั้ง
ฉันขอแนะนำให้ใช้ที่นี่เป็นเขตข้อมูลคงที่

Public class MyDao {

   private static RowMapper myRowMapper = new MyRowMapper();
   private String sql = "select * from MY_ITEM";

   public List<MyItem> getAllItems() {
       springJdbcTemplate.query(sql, myRowMapper);
   };
};
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.