2011-12-18 15 views
12

अपने प्रदर्शन को बेहतर बनाने के लिए, मैं 20ms की न्यूनतम नमूना अवधि का उपयोग करके, VisualVM नमूने के साथ अपने अनुप्रयोगों में से एक को प्रोफाइल कर रहा हूं। प्रोफाइलर के अनुसार, मुख्य धागा DecimalFormat.format() विधि में अपने CPU समय का लगभग एक चौथाई खर्च करता है।DecimalFormat.format() के लिए एक तेज विकल्प?

मैं 0.000000 के साथ double संख्याओं को "छह" अंकों के साथ एक स्ट्रिंग प्रतिनिधित्व के लिए "रूपांतरित" करने के लिए पैटर्न का उपयोग कर रहा हूं। मुझे पता है कि यह विधि अपेक्षाकृत महंगी है और कई बार बुलाया गया है, लेकिन मैं अभी भी इन परिणामों से कुछ हद तक आश्चर्यचकित था।

  1. ऐसे नमूने प्रोफाइलर के परिणाम किस डिग्री के लिए सटीक हैं? मैं उन्हें सत्यापित करने के बारे में कैसे जाउंगा - प्राथमिक रूप से एक उपकरण प्रोफाइलर का उपयोग किए बिना?

  2. क्या मेरे उपयोग के मामले में DecimalFormat का कोई तेज़ विकल्प है? क्या यह मेरे NumberFormat सबक्लास को रोल करने के लिए समझ में आता है?

अद्यतन:

  • DecimalFormat.format():: एकल DecimalFormat वस्तु पुन: उपयोग किया कई बार

    मैं निम्नलिखित तीन तरीकों के प्रदर्शन की तुलना एक माइक्रो बेंचमार्क बनाया।

  • String.format(): एकाधिक स्वतंत्र कॉल। आंतरिक रूप से इस विधि को

    public static String format(String format, Object ... args) { 
        return new Formatter().format(format, args).toString(); 
    } 
    

    इसलिए मैं अपने प्रदर्शन बहुत Formatter.format() के समान होने की उम्मीद निर्भर करता है।

  • Formatter.format(): सिंगल Formatter ऑब्जेक्ट कई बार पुन: उपयोग किया गया।

    इस विधि थोड़ा अजीब है - Formatter वस्तुओं डिफ़ॉल्ट निर्माता के साथ बनाई गई एक आंतरिक StringBuilder वस्तु है, जो ठीक से पहुंच योग्य नहीं है format() विधि द्वारा बनाई गई सभी स्ट्रिंग्स संलग्न करें और इसलिए साफ़ नहीं किया जा सकता है। नतीजतन, format() पर कई कॉल सभी परिणामी तारों के बनाएंगे।

    इस समस्या के आसपास काम करने के लिए, मैंने अपना खुद का StringBuilder उदाहरण प्रदान किया है जिसे मैंने setLength(0) कॉल के उपयोग से पहले साफ़ कर दिया था।

परिणाम जहां दिलचस्प:

  • DecimalFormat.format() प्रति कॉल 1.4us पर आधारभूत था।
  • String.format() प्रति कॉल 2.7us पर दो के कारक से धीमा था।
  • Formatter.format() 2.5us प्रति कॉल पर दो के कारक द्वारा भी धीमा था।

अभी यह लगता है कि DecimalFormat.format() अभी भी इन विकल्पों में सबसे तेज़ है।

उत्तर

9

आप अपना खुद का दिनचर्या लिख ​​सकते हैं जो आपको पता है कि आप वास्तव में क्या चाहते हैं।

public static void appendTo6(StringBuilder builder, double d) { 
    if (d < 0) { 
     builder.append('-'); 
     d = -d; 
    } 
    if (d * 1e6 + 0.5 > Long.MAX_VALUE) { 
     // TODO write a fall back. 
     throw new IllegalArgumentException("number too large"); 
    } 
    long scaled = (long) (d * 1e6 + 0.5); 
    long factor = 1000000; 
    int scale = 7; 
    long scaled2 = scaled/10; 
    while (factor <= scaled2) { 
     factor *= 10; 
     scale++; 
    } 
    while (scale > 0) { 
     if (scale == 6) 
      builder.append('.'); 
     long c = scaled/factor % 10; 
     factor /= 10; 
     builder.append((char) ('0' + c)); 
     scale--; 
    } 
} 

@Test 
public void testCases() { 
    for (String s : "-0.000001,0.000009,-0.000010,0.100000,1.100000,10.100000".split(",")) { 
     double d = Double.parseDouble(s); 
     StringBuilder sb = new StringBuilder(); 
     appendTo6(sb, d); 
     assertEquals(s, sb.toString()); 
    } 
} 

public static void main(String[] args) { 
    StringBuilder sb = new StringBuilder(); 
    long start = System.nanoTime(); 
    final int runs = 20000000; 
    for (int i = 0; i < runs; i++) { 
     appendTo6(sb, i * 1e-6); 
     sb.setLength(0); 
    } 
    long time = System.nanoTime() - start; 
    System.out.printf("Took %,d ns per append double%n", time/runs); 
} 

प्रिंट

Took 128 ns per append double 

आप और भी अधिक प्रदर्शन आप एक सीधा ByteBuffer को लिख सकते हैं (यह मानते हुए आप डेटा कहीं लिखना चाहते हैं) तो डेटा आप का उत्पादन या कॉपी करने के लिए एन्कोडेड जरूरत नहीं चाहते हैं । (मान लीजिए कि ठीक है)

नोट: यह 9 ट्रिलियन (Long.MAX_VALUE/1e6) से कम के सकारात्मक/नकारात्मक मानों तक ही सीमित है यदि आप कोई समस्या हो तो आप विशेष हैंडलिंग जोड़ सकते हैं।

+2

+1 मैं खुद कुछ लिखने वाला था - कोड का यह टुकड़ा एक अच्छा प्रारंभिक बिंदु हो सकता है। – thkala

+1

मैंने अंत में अपने स्वयं के एल्गोरिदम का उपयोग करके एक फॉर्मेटर लिखा और यह मुझे आवश्यक कार्यक्षमता सबसेट के लिए 'दशमलव Format' से लगभग चार गुना तेज है। मेरा मानना ​​है कि अभी भी सुधार के लिए जगह है, क्योंकि मैं वास्तव में व्यक्तिगत अंकों को जोड़ने के स्तर पर नहीं गया था। मैं इस जवाब को स्वीकार करूंगा क्योंकि यह एकमात्र ऐसा है जिसमें उपयोग करने योग्य कोड शामिल है। – thkala

+0

ग्रेट जॉब पीटर। मैंने तेज़ 'डबल' बनाने के लिए अपना कोड दोबारा उपयोग किया है और यहां तक ​​कि तेज़ 'स्ट्रिंग' (डबल वैल्यू) फॉर्मेटर भी बंद कर दिया है। – dantuch

2

शायद आपका प्रोग्राम बहुत गहन काम नहीं करता है और इसलिए ऐसा लगता है कि कुछ संख्याओं में सबसे अधिक क्रंचिंग होती है।

मेरा मुद्दा यह है कि आपके परिणाम अभी भी आपके ऐप के सापेक्ष हैं।

प्रत्येक दशमलव Formatter.format() के चारों ओर एक टाइमर रखें और देखें कि आप एक स्पष्ट तस्वीर प्राप्त करने के लिए कितने मिल का उपयोग कर रहे हैं।

लेकिन अगर आप अभी भी इसके बारे में चिंतित हैं, यहाँ एक लेख है आपको पसंद आ सकते:
http://onjava.com/pub/a/onjava/2000/12/15/formatting_doubles.html

+0

लेख लिंक के लिए +1 - मुझे शायद अपना खुद का फॉर्मेटर कार्यान्वयन लिखना होगा। – thkala

0

एक वैकल्पिक स्ट्रिंग Formatter उपयोग करने के लिए है, यह अगर यह बेहतर प्रदर्शन करती है यह देखने के लिए आजमाइए होगा:

String.format("%.6f", 1.23456789) 

या और भी बेहतर, एक भी फ़ॉर्मेटर बनाने और इसे पुन: उपयोग - जब तक वहाँ कोई बहु सूत्रण मुद्दे हैं, के बाद से formatters बहु पहुँच के लिए जरूरी सुरक्षित नहीं हैं:

Formatter formatter = new Formatter(); 
// presumably, the formatter would be called multiple times 
System.out.println(formatter.format("%.6f", 1.23456789)); 
formatter.close(); 
+1

पुन: उपयोग करना अच्छा होगा लेकिन फॉर्मेटर्स थ्रेड सुरक्षित नहीं हैं, इसलिए आपको यह जांचना होगा कि क्या यह विशेष फ़ॉर्मेटर एकाधिक थ्रेड (उदाहरण के लिए एक वेब एप्लिकेशन में) को संभाल सकता है – extraneon

+0

@extraneon टिप्पणी के लिए धन्यवाद, मैंने तदनुसार अपना जवाब संपादित किया । –

+1

दोनों 'स्ट्रिंग.फॉर्मैट()' और 'फॉर्मेटर.फॉर्मैट()' 'दशमलव Format.format()' से धीमे प्रतीत होते हैं। मुझे संदेह है क्योंकि पैटर्न स्ट्रिंग को हर बार पार्स किया जाना चाहिए। 'Formatter.format() 'का पुन: उपयोग करना भी कठिन है - विवरण के लिए मेरा संपादन देखें। – thkala

0

स्वीकार्य उत्तर (अपना स्वयं का कस्टम फॉर्मेटर लिखें) सही है लेकिन ओपी का वांछित प्रारूप कुछ असामान्य है, तो शायद यह दूसरों के लिए सहायक नहीं होगा?

यहां संख्याओं के लिए एक कस्टम कार्यान्वयन है कि: अल्पविराम विभाजक की आवश्यकता होती है; दो दशमलव स्थानों तक है। यह एंटरप्राइज़-मुद्राओं और प्रतिशत जैसे चीजों के लिए उपयोगी है।

/** 
* Formats a decimal to either zero (if an integer) or two (even if 0.5) decimal places. Useful 
* for currency. Also adds commas. 
* <p> 
* Note: Java's <code>DecimalFormat</code> is neither Thread-safe nor particularly fast. This is our attempt to improve it. Basically we pre-render a bunch of numbers including their 
* commas, then concatenate them. 
*/ 

private final static String[] PRE_FORMATTED_INTEGERS = new String[500_000]; 

static { 
    for (int loop = 0, length = PRE_FORMATTED_INTEGERS.length; loop < length; loop++) { 

     StringBuilder builder = new StringBuilder(Integer.toString(loop)); 

     for (int loop2 = builder.length() - 3; loop2 > 0; loop2 -= 3) { 
      builder.insert(loop2, ','); 
     } 

     PRE_FORMATTED_INTEGERS[loop] = builder.toString(); 
    } 
} 

public static String formatShortDecimal(Number decimal, boolean removeTrailingZeroes) { 

    if (decimal == null) { 
     return "0"; 
    } 

    // Use PRE_FORMATTED_INTEGERS directly for short integers (fast case) 

    boolean isNegative = false; 

    int intValue = decimal.intValue(); 
    double remainingDouble; 

    if (intValue < 0) { 
     intValue = -intValue; 
     remainingDouble = -decimal.doubleValue() - intValue; 
     isNegative = true; 
    } else { 
     remainingDouble = decimal.doubleValue() - intValue; 
    } 

    if (remainingDouble > 0.99) { 
     intValue++; 
     remainingDouble = 0; 
    } 

    if (intValue < PRE_FORMATTED_INTEGERS.length && remainingDouble < 0.01 && !isNegative) { 
     return PRE_FORMATTED_INTEGERS[intValue]; 
    } 

    // Concatenate our pre-formatted numbers for longer integers 

    StringBuilder builder = new StringBuilder(); 

    while (true) { 
     if (intValue < PRE_FORMATTED_INTEGERS.length) { 
      String chunk = PRE_FORMATTED_INTEGERS[intValue]; 
      builder.insert(0, chunk); 
      break; 
     } 
     int nextChunk = intValue/1_000; 
     String chunk = PRE_FORMATTED_INTEGERS[intValue - (nextChunk * 1_000) + 1_000]; 
     builder.insert(0, chunk, 1, chunk.length()); 
     intValue = nextChunk; 
    } 

    // Add two decimal places (if any) 

    if (remainingDouble >= 0.01) { 
     builder.append('.'); 
     intValue = (int) Math.round((remainingDouble + 1) * 100); 
     builder.append(PRE_FORMATTED_INTEGERS[intValue], 1, PRE_FORMATTED_INTEGERS[intValue].length()); 

     if (removeTrailingZeroes && builder.charAt(builder.length() - 1) == '0') { 
      builder.deleteCharAt(builder.length() - 1); 
     } 
    } 

    if (isNegative) { 
     builder.insert(0, '-'); 
    } 

    return builder.toString(); 
} 

इस माइक्रो बेंचमार्क से पता चलता है कि यह (लेकिन निश्चित रूप YMMV के आपके उपयोग के मामले के आधार पर) 2x DecimalFormat तुलना में तेजी से किया जाना है। सुधार स्वागत है!

/** 
* Micro-benchmark for our custom <code>DecimalFormat</code>. When profiling, we spend a 
* surprising amount of time in <code>DecimalFormat</code>, as noted here 
* https://bugs.openjdk.java.net/browse/JDK-7050528. It is also not Thread-safe. 
* <p> 
* As recommended here 
* http://stackoverflow.com/questions/8553672/a-faster-alternative-to-decimalformat-format 
* we can write a custom format given we know exactly what output we want. 
* <p> 
* Our code benchmarks around 2x as fast as <code>DecimalFormat</code>. See micro-benchmark 
* below. 
*/ 

public static void main(String[] args) { 

    Random random = new Random(); 
    DecimalFormat format = new DecimalFormat("###,###,##0.##"); 

    for (int warmup = 0; warmup < 100_000_000; warmup++) { 
     MathUtils.formatShortDecimal(random.nextFloat() * 100_000_000); 
     format.format(random.nextFloat() * 100_000_000); 
    } 

    // DecimalFormat 

    long start = System.currentTimeMillis(); 

    for (int test = 0; test < 100_000_000; test++) { 
     format.format(random.nextFloat() * 100_000_000); 
    } 

    long end = System.currentTimeMillis(); 
    System.out.println("DecimalFormat: " + (end - start) + "ms"); 

    // Custom 

    start = System.currentTimeMillis(); 

    for (int test = 0; test < 100_000_000; test++) { 
     MathUtils.formatShortDecimal(random.nextFloat() * 100_000_000); 
    } 

    end = System.currentTimeMillis(); 
    System.out.println("formatShortDecimal: " + (end - start) + "ms"); 
}