জাভা 8: Class.getName () স্ট্রিং কনক্যাটেন্টেশন চেইনকে ধীর করে দেয়


13

সম্প্রতি আমি স্ট্রিং কনটেক্সটেশন সম্পর্কিত একটি সমস্যা নিয়ে এসেছি। এই মানদণ্ডটি এর সংক্ষিপ্তসার দেয়:

@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class BrokenConcatenationBenchmark {

  @Benchmark
  public String slow(Data data) {
    final Class<? extends Data> clazz = data.clazz;
    return "class " + clazz.getName();
  }

  @Benchmark
  public String fast(Data data) {
    final Class<? extends Data> clazz = data.clazz;
    final String clazzName = clazz.getName();
    return "class " + clazzName;
  }

  @State(Scope.Thread)
  public static class Data {
    final Class<? extends Data> clazz = getClass();

    @Setup
    public void setup() {
      //explicitly load name via native method Class.getName0()
      clazz.getName();
    }
  }
}

JDK 1.8.0_222 এ (ওপেনজেডকে 64-বিট সার্ভার ভিএম, 25.222-বি 10) আমি নিম্নলিখিত ফলাফল পেয়েছি:

Benchmark                                                            Mode  Cnt     Score     Error   Units
BrokenConcatenationBenchmark.fast                                    avgt   25    22,253 ±   0,962   ns/op
BrokenConcatenationBenchmark.fastgc.alloc.rate                     avgt   25  9824,603 ± 400,088  MB/sec
BrokenConcatenationBenchmark.fastgc.alloc.rate.norm                avgt   25   240,000 ±   0,001    B/op
BrokenConcatenationBenchmark.fastgc.churn.PS_Eden_Space            avgt   25  9824,162 ± 397,745  MB/sec
BrokenConcatenationBenchmark.fastgc.churn.PS_Eden_Space.norm       avgt   25   239,994 ±   0,522    B/op
BrokenConcatenationBenchmark.fastgc.churn.PS_Survivor_Space        avgt   25     0,040 ±   0,011  MB/sec
BrokenConcatenationBenchmark.fastgc.churn.PS_Survivor_Space.norm   avgt   25     0,001 ±   0,001    B/op
BrokenConcatenationBenchmark.fastgc.count                          avgt   25  3798,000            counts
BrokenConcatenationBenchmark.fastgc.time                           avgt   25  2241,000                ms

BrokenConcatenationBenchmark.slow                                    avgt   25    54,316 ±   1,340   ns/op
BrokenConcatenationBenchmark.slowgc.alloc.rate                     avgt   25  8435,703 ± 198,587  MB/sec
BrokenConcatenationBenchmark.slowgc.alloc.rate.norm                avgt   25   504,000 ±   0,001    B/op
BrokenConcatenationBenchmark.slowgc.churn.PS_Eden_Space            avgt   25  8434,983 ± 198,966  MB/sec
BrokenConcatenationBenchmark.slowgc.churn.PS_Eden_Space.norm       avgt   25   503,958 ±   1,000    B/op
BrokenConcatenationBenchmark.slowgc.churn.PS_Survivor_Space        avgt   25     0,127 ±   0,011  MB/sec
BrokenConcatenationBenchmark.slowgc.churn.PS_Survivor_Space.norm   avgt   25     0,008 ±   0,001    B/op
BrokenConcatenationBenchmark.slowgc.count                          avgt   25  3789,000            counts
BrokenConcatenationBenchmark.slowgc.time                           avgt   25  2245,000                ms

এটি JDK-8043677 এর মতো একটি ইস্যুর মতো দেখায় , যেখানে পার্শ্ব প্রতিক্রিয়াযুক্ত একটি এক্সপ্রেশনটি নতুন StringBuilder.append().append().toString()চেইনের অপ্টিমাইজেশনকে ভেঙে দেয় । তবে Class.getName()নিজের কোডটিতে কোনও পার্শ্ব প্রতিক্রিয়া রয়েছে বলে মনে হয় না:

private transient String name;

public String getName() {
  String name = this.name;
  if (name == null) {
    this.name = name = this.getName0();
  }

  return name;
}

private native String getName0();

এখানে কেবল সন্দেহজনক জিনিসটি হ'ল দেশীয় পদ্ধতিতে কল যা বাস্তবে কেবল একবার ঘটে এবং এর ফলশ্রুতি শ্রেণীর ক্ষেত্রে ক্যাশে। আমার মানদণ্ডে আমি সেটআপ পদ্ধতিতে এটি স্পষ্টভাবে ক্যাশে করেছি।

আমি প্রত্যাশা করেছি শাখার ভবিষ্যদ্বাণীটি সনাক্ত করতে যে প্রতিটি মানদণ্ডে এই নামটির আসল মানটি কখনই বাতিল হয় না এবং পুরো এক্সপ্রেশনটি অনুকূল করে তোলে।

যাইহোক, BrokenConcatenationBenchmark.fast()আমার কাছে থাকাকালীন :

@ 19   tsypanov.strings.benchmark.concatenation.BrokenConcatenationBenchmark::fast (30 bytes)   force inline by CompileCommand
  @ 6   java.lang.Class::getName (18 bytes)   inline (hot)
    @ 14   java.lang.Class::initClassName (0 bytes)   native method
  @ 14   java.lang.StringBuilder::<init> (7 bytes)   inline (hot)
  @ 19   java.lang.StringBuilder::append (8 bytes)   inline (hot)
  @ 23   java.lang.StringBuilder::append (8 bytes)   inline (hot)
  @ 26   java.lang.StringBuilder::toString (35 bytes)   inline (hot)

উদাহরণস্বরূপ সংকলক সবকিছুকে ইনলাইন করতে সক্ষম, কারণ BrokenConcatenationBenchmark.slow()এটি আলাদা:

@ 19   tsypanov.strings.benchmark.concatenation.BrokenConcatenationBenchmark::slow (28 bytes)   force inline by CompilerOracle
  @ 9   java.lang.StringBuilder::<init> (7 bytes)   inline (hot)
    @ 3   java.lang.AbstractStringBuilder::<init> (12 bytes)   inline (hot)
      @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
  @ 14   java.lang.StringBuilder::append (8 bytes)   inline (hot)
    @ 2   java.lang.AbstractStringBuilder::append (50 bytes)   inline (hot)
      @ 10   java.lang.String::length (6 bytes)   inline (hot)
      @ 21   java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes)   inline (hot)
        @ 17   java.lang.AbstractStringBuilder::newCapacity (39 bytes)   inline (hot)
        @ 20   java.util.Arrays::copyOf (19 bytes)   inline (hot)
          @ 11   java.lang.Math::min (11 bytes)   (intrinsic)
          @ 14   java.lang.System::arraycopy (0 bytes)   (intrinsic)
      @ 35   java.lang.String::getChars (62 bytes)   inline (hot)
        @ 58   java.lang.System::arraycopy (0 bytes)   (intrinsic)
  @ 18   java.lang.Class::getName (21 bytes)   inline (hot)
    @ 11   java.lang.Class::getName0 (0 bytes)   native method
  @ 21   java.lang.StringBuilder::append (8 bytes)   inline (hot)
    @ 2   java.lang.AbstractStringBuilder::append (50 bytes)   inline (hot)
      @ 10   java.lang.String::length (6 bytes)   inline (hot)
      @ 21   java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes)   inline (hot)
        @ 17   java.lang.AbstractStringBuilder::newCapacity (39 bytes)   inline (hot)
        @ 20   java.util.Arrays::copyOf (19 bytes)   inline (hot)
          @ 11   java.lang.Math::min (11 bytes)   (intrinsic)
          @ 14   java.lang.System::arraycopy (0 bytes)   (intrinsic)
      @ 35   java.lang.String::getChars (62 bytes)   inline (hot)
        @ 58   java.lang.System::arraycopy (0 bytes)   (intrinsic)
  @ 24   java.lang.StringBuilder::toString (17 bytes)   inline (hot)

সুতরাং প্রশ্নটি হচ্ছে এটি জেভিএম বা সংকলক বাগের উপযুক্ত আচরণ কিনা?

আমি প্রশ্নটি জিজ্ঞাসা করছি কারণ কিছু প্রকল্প এখনও জাভা 8 ব্যবহার করছে এবং এটি যদি রিলিজ আপডেটগুলির কোনওটিতে স্থির না করা হয় তবে আমার কাছে Class.getName()হট স্পটগুলি থেকে ম্যানুয়ালি কল উত্তোলন করা যুক্তিসঙ্গত ।

পিএস সর্বশেষ জেডিকে (11, 13, 14-এপ) এ সমস্যাটি পুনরুত্পাদন করা হয় না।


অ্যাসাইনমেন্ট - আপনার সেখানে কোনও পার্শ্ব প্রতিক্রিয়া আছে this.name
রিয়েলস্কেপটিক

@ রিলেসসেকটিক অ্যাসাইনমেন্টটি কেবল একবার Class.getName()এবং setUp()পদ্ধতিতে প্রথমবার অনুরোধ করা হয় , বেঞ্চমার্কযুক্ত ব্যক্তির শরীরে নয়।
সের্গেই শিপানোভ

উত্তর:


7

হটস্পট জেভিএম বাইকোডে কার্যকরকরণের পরিসংখ্যান সংগ্রহ করে। যদি একই কোডটি বিভিন্ন প্রসঙ্গে চালানো হয়, ফলাফল প্রোফাইলটি সমস্ত প্রসংগের পরিসংখ্যানকে একত্রিত করবে। এই প্রভাবটি প্রোফাইল দূষণ হিসাবে পরিচিত ।

Class.getName()স্পষ্টতই কেবল আপনার বেঞ্চমার্ক কোড থেকে নয় called জেআইটি মানদণ্ড সংকলন শুরু করার আগেই এটি ইতিমধ্যে জানে যে নিম্নলিখিত শর্তটি Class.getName()একাধিকবার পূরণ করা হয়েছিল:

    if (name == null)
        this.name = name = getName0();

অন্ততপক্ষে, এই শাখার পরিসংখ্যানগতভাবে গুরুত্বপূর্ণ হিসাবে চিকিত্সার জন্য পর্যাপ্ত সময়। সুতরাং, জেআইটি এই শাখাটি সংকলন থেকে বাদ দেয়নি এবং এভাবে সম্ভাব্য পার্শ্ব প্রতিক্রিয়ার কারণে স্ট্রিং কনক্যাটটি অনুকূল করতে পারেনি।

এটি এমনকি একটি স্থানীয় পদ্ধতি কল করার প্রয়োজন হয় না। কেবলমাত্র একটি নিয়মিত ক্ষেত্রের কার্যভারকে পার্শ্ব প্রতিক্রিয়া হিসাবে বিবেচনা করা হয়।

প্রোফাইল দূষণ কীভাবে আরও অনুকূলকরণের ক্ষতি করতে পারে তার একটি উদাহরণ এখানে।

@State(Scope.Benchmark)
public class StringConcat {
    private final MyClass clazz = new MyClass();

    static class MyClass {
        private String name;

        public String getName() {
            if (name == null) name = "ZZZ";
            return name;
        }
    }

    @Param({"1", "100", "400", "1000"})
    private int pollutionCalls;

    @Setup
    public void setup() {
        for (int i = 0; i < pollutionCalls; i++) {
            new MyClass().getName();
        }
    }

    @Benchmark
    public String fast() {
        String clazzName = clazz.getName();
        return "str " + clazzName;
    }

    @Benchmark
    public String slow() {
        return "str " + clazz.getName();
    }
}

এটি মূলত আপনার বেনমার্কের পরিবর্তিত সংস্করণ যা getName()প্রোফাইলের দূষণকে অনুকরণ করে । getName()একটি তাজা অবজেক্টে প্রাথমিক কলগুলির সংখ্যার উপর নির্ভর করে স্ট্রিং কনটেন্টেশনটির পরবর্তী কর্মক্ষমতা নাটকীয়ভাবে পৃথক হতে পারে:

Benchmark          (pollutionCalls)  Mode  Cnt   Score   Error  Units
StringConcat.fast                 1  avgt   15  11,458 ± 0,076  ns/op
StringConcat.fast               100  avgt   15  11,690 ± 0,222  ns/op
StringConcat.fast               400  avgt   15  12,131 ± 0,105  ns/op
StringConcat.fast              1000  avgt   15  12,194 ± 0,069  ns/op
StringConcat.slow                 1  avgt   15  11,771 ± 0,105  ns/op
StringConcat.slow               100  avgt   15  11,963 ± 0,212  ns/op
StringConcat.slow               400  avgt   15  26,104 ± 0,202  ns/op  << !
StringConcat.slow              1000  avgt   15  26,108 ± 0,436  ns/op  << !

প্রোফাইল দূষণের আরও উদাহরণ »

আমি এটিকে কোনও বাগ বা "উপযুক্ত আচরণ" বলতে পারি না। এটি হটস্পটে কীভাবে গতিশীল অভিযোজিত সংকলন প্রয়োগ করা হয়।


1
পাঙ্গিন না হলে আর কে না ... গ্রাল সি 2 এরও একই অসুস্থতা আছে কিনা তা আপনি জানতে পেরেছেন?
ইউজিন

1

কিছুটা সম্পর্কযুক্ত নয় তবে জাভা 9 এবং জেপি 280 সাল থেকে : স্ট্রিং কনটেনটেশনকে ইন্ডিফাই করুন স্ট্রিং কনটেনটেশন এখন করা হয়েছে invokedynamicএবং নেই StringBuilderএই নিবন্ধটি জাভা 8 এবং জাভা 9 এর মধ্যে বাইটকোডের পার্থক্যগুলি দেখায়।

যদি নতুন জাভা সংস্করণে পুনরায় চালিত বেঞ্চমার্ক সমস্যাটি না দেখায় তবে বেশিরভাগ লাইকলে কোনও বাগ নেই javacকারণ সংকলকটি এখন নতুন পদ্ধতি ব্যবহার করে। জাভা 8 আচরণে ডাইভিং করা যদি নতুন সংস্করণগুলিতে এরকম উল্লেখযোগ্য পরিবর্তন ঘটে তবে তা কার্যকর।


1
আমি সম্মত এটি সম্ভবত একটি সংকলক সমস্যা হতে পারে, javacযদিও সম্পর্কিত কোনও নয় । javacবাইকোড উত্পন্ন করে এবং কোনও পরিশীলিত অপ্টিমাইজেশন করে না। আমি একই -XX:TieredStopAtLevel=1আউটপুটটির সাথে চালিয়েছি এবং এই আউটপুটটি পেয়েছি: Benchmark Mode Cnt Score Error Units BrokenConcatenationBenchmark.fast avgt 25 74,677 ? 2,961 ns/op BrokenConcatenationBenchmark.slow avgt 25 69,316 ? 1,239 ns/op সুতরাং আমরা যখন উভয় পদ্ধতিতে একই ফলাফল অর্জন করি না তখন কোডটি সি 2-সংকলিত হয়ে গেলেই সমস্যাটি নিজেকে প্রকাশ করে।
সের্গেই শিপানোভ

1
স্ট্রিংবিল্ডার না হয়ে এখন ইনভোকডিনামিক দিয়ে কাজ করা সহজভাবে ভুলinvokedynamicসংক্ষিপ্তকরণ কীভাবে করবেন তা চয়ন করতে কেবল রানটাইমকে বলে এবং 6 টির মধ্যে 5 টি কৌশল (ডিফল্ট সহ) এখনও ব্যবহার করে StringBuilder
ইউজিন

এই ইঙ্গিত করার জন্য @ ইউজিন ধন্যবাদ। আপনি যখন বলছেন কৌশলগুলি কি আপনার মানে StringConcatFactory.Strategyএনুম?
কারোল ডউবেকি

পছন্দ করুন
ইউজিন
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.