लाखों छोटी अस्थायी वस्तुओं को बनाने के लिए सबसे अच्छा अभ्यास


109

लाखों छोटी वस्तुओं को बनाने (और जारी करने) के लिए "सर्वोत्तम अभ्यास" क्या हैं?

मैं जावा में एक शतरंज कार्यक्रम लिख रहा हूं और खोज एल्गोरिथ्म प्रत्येक संभव कदम के लिए एक "मूव" ऑब्जेक्ट उत्पन्न करता है, और एक नाममात्र खोज आसानी से प्रति सेकंड एक मिलियन चाल वस्तुओं को उत्पन्न कर सकता है। जेवीएम जीसी मेरी विकास प्रणाली पर भार को संभालने में सक्षम है, लेकिन मैं वैकल्पिक दृष्टिकोणों की खोज करने में रुचि रखता हूं:

  1. कचरा संग्रहण के ओवरहेड को छोटा करें, और
  2. लोअर-एंड सिस्टम के लिए पीक मेमोरी फुटप्रिंट को कम करें।

वस्तुओं का एक विशाल बहुमत बहुत कम रहता है, लेकिन उत्पन्न चाल के लगभग 1% को बरकरार रखा जाता है और निरंतर मूल्य के रूप में वापस किया जाता है, इसलिए किसी भी पूलिंग या कैशिंग तकनीक को विशिष्ट वस्तुओं को फिर से उपयोग किए जाने की क्षमता प्रदान करनी होगी। ।

मुझे उदाहरण कोड से पूरी तरह से मेल खाने की उम्मीद नहीं है, लेकिन मैं आगे पढ़ने / अनुसंधान के लिए सुझावों की सराहना करता हूं, या समान प्रकृति के ओपन सोर्स उदाहरण।


11
क्या फ्लाईवेट पैटर्न आपके मामले के लिए उपयुक्त होगा? en.wikipedia.org/wiki/Flyweight_pattern
रोजर रोलैंड

4
क्या आपको इसे किसी वस्तु में संलग्न करने की आवश्यकता है?
न्हाथ्ठ

1
फ्लाईवेट पैटर्न उचित नहीं है, क्योंकि ऑब्जेक्ट महत्वपूर्ण सामान्य डेटा साझा नहीं करते हैं। किसी ऑब्जेक्ट में डेटा एनकैप्सुलेट करने के लिए, यह एक आदिम में पैक होने के लिए बहुत बड़ा है, यही कारण है कि मैं पीओजेओ के विकल्प की तलाश कर रहा हूं।
विनम्र प्रोग्रामर

2
अत्यधिक अनुशंसित पढ़ने के लिए: cs.virginia.edu/kim/publicity/pldi09tutorials/…
rkj

जवाबों:


47

वर्बोज़ कचरा संग्रह के साथ एप्लिकेशन चलाएँ:

java -verbose:gc

और यह आपको बताएगा कि यह कब एकत्रित होता है। इसमें दो तरह के स्वीप होंगे, एक फास्ट और एक पूरा स्वीप।

[GC 325407K->83000K(776768K), 0.2300771 secs]
[GC 325816K->83372K(776768K), 0.2454258 secs]
[Full GC 267628K->83769K(776768K), 1.8479984 secs]

तीर आकार से पहले और बाद में है।

जब तक यह सिर्फ जीसी कर रहा है और पूर्ण जीसी नहीं है आप घर सुरक्षित हैं। नियमित जीसी 'युवा पीढ़ी' में एक कॉपी कलेक्टर है, इसलिए जिन वस्तुओं को अब संदर्भित नहीं किया जाता है, वे बस के बारे में भूल जाते हैं, जो वास्तव में आप चाहते हैं।

जावा एसई 6 हॉटस्पॉट वर्चुअल मशीन कचरा संग्रह ट्यूनिंग पढ़ना संभवतः सहायक है।


एक बिंदु को खोजने की कोशिश करने के लिए जावा हीप आकार के साथ प्रयोग जहां पूर्ण कचरा संग्रह दुर्लभ है। जावा 7 में नया G1 GC कुछ मामलों में तेज है (और अन्य में धीमा)।
माइकल शोप्सिन

21

संस्करण 6 के बाद से, जेवीएम का सर्वर मोड एक भागने विश्लेषण तकनीक को नियुक्त करता है । इसके इस्तेमाल से आप जीसी से एक साथ बच सकते हैं।


1
एस्केप विश्लेषण अक्सर निराश करता है, यह जांचने योग्य है कि क्या जेवीएम ने यह पता लगाया है कि आप क्या कर रहे हैं या नहीं।
निट्सन वकार्ट

2
यदि आपके पास इन विकल्प का उपयोग करने का अनुभव है: -XX: + PrintEscapeAnalysis और -XX: + PrintEliminateAnocations। यह साझा करने के लिए बहुत अच्छा होगा। क्योंकि मैं ईमानदारी से नहीं कह रहा हूं।
मिखाइल

देखें stackoverflow.com/questions/9032519/… आपको JDK 7 के लिए डिबग बिल्ड प्राप्त करने की आवश्यकता होगी, मैं मानता हूं कि मैंने ऐसा नहीं किया है, लेकिन JDK 6 के साथ यह सफल रहा है।
निट्सन वकार्ट

19

खैर, यहाँ एक में कई सवाल हैं!

1 - अल्पकालिक वस्तुओं का प्रबंधन कैसे किया जाता है?

जैसा कि पहले कहा गया था, जेवीएम पूरी तरह से अल्पकालिक वस्तु की एक बड़ी मात्रा से निपट सकता है, क्योंकि यह कमजोर पीढ़ी की परिकल्पना का अनुसरण करता है ।

ध्यान दें कि हम उन वस्तुओं की बात कर रहे हैं जो मुख्य मेमोरी (ढेर) तक पहुंच गए हैं। ऐसी स्थिति हर बार नहीं होती है। आपके द्वारा बनाई गई बहुत सी वस्तुएँ सीपीयू रजिस्टर को भी नहीं छोड़ती हैं। उदाहरण के लिए, इस लूप के लिए विचार करें

for(int i=0, i<max, i++) {
  // stuff that implies i
}

चलो लूप के अनियंत्रित होने के बारे में नहीं सोचते हैं (एक अनुकूलन जो आपके कोड पर जेवीएम भारी प्रदर्शन करता है)। यदि maxइसके बराबर है Integer.MAX_VALUE, तो आपको निष्पादित करने में कुछ समय लग सकता है। हालांकि, iचर लूप-ब्लॉक से कभी नहीं बच पाएगा। इसलिए जेवीएम उस चर को सीपीयू रजिस्टर में डाल देगा, इसे नियमित रूप से बढ़ाएगा लेकिन इसे मुख्य मेमोरी में वापस नहीं भेजेगा।

इसलिए, लाखों वस्तुओं का निर्माण एक बड़ी बात नहीं है यदि उनका उपयोग केवल स्थानीय रूप से किया जाता है। ईडन में संग्रहीत होने से पहले वे मृत हो जाएंगे, इसलिए जीसी भी उन्हें नोटिस नहीं करेंगे।

2 - क्या जीसी के ओवरहेड को कम करना उपयोगी है?

हमेशा की तरह, यह निर्भर करता है।

सबसे पहले, आपको GC लॉगिंग के बारे में स्पष्ट दृष्टिकोण रखने में सक्षम होना चाहिए। आप इसे सक्षम कर सकते हैं -Xloggc:gc.log -XX:+PrintGCDetails

यदि आपका आवेदन जीसी चक्र में बहुत समय बिता रहा है, तो, हाँ, जीसी को ट्यून करें, अन्यथा, यह वास्तव में इसके लायक नहीं हो सकता है।

उदाहरण के लिए, यदि आपके पास हर 100ms में एक युवा GC है जो 10ms लेता है, तो आप अपना 10% समय GC में बिताते हैं, और आपके पास प्रति सेकंड 10 संग्रह हैं (जो कि Huuuuuge है)। ऐसे मामले में, मैं जीसी ट्यूनिंग में कोई समय नहीं बिताऊंगा, क्योंकि 10 जीसी / एस अभी भी वहां मौजूद हैं।

3 - कुछ अनुभव

मुझे एक आवेदन पर एक समान समस्या थी जो एक दिए गए वर्ग की एक बड़ी राशि बना रही थी। जीसी लॉग में, मैंने देखा कि एप्लिकेशन की निर्माण दर लगभग 3 जीबी / एस थी, जो कि बहुत अधिक है (चलो ... हर सेकंड 3 गीगाबाइट डेटा!)।

समस्या: बहुत अधिक लगातार जीसी के कारण बहुत अधिक वस्तुओं का निर्माण हो रहा है।

मेरे मामले में, मैंने एक मेमोरी प्रोफाइलर को संलग्न किया और देखा कि एक वर्ग ने मेरी सभी वस्तुओं का एक बड़ा प्रतिशत प्रतिनिधित्व किया। मैंने यह जानने के लिए तात्कालिकता पर नज़र रखी कि यह वर्ग मूल रूप से एक वस्तु में लिपटे बूलियनों की एक जोड़ी थी। उस मामले में, दो समाधान उपलब्ध थे:

  • एल्गोरिथ्म को फिर से तैयार करें ताकि मैं एक जोड़ी बूलियन वापस न कर दूं लेकिन इसके बजाय मेरे पास दो तरीके हैं जो प्रत्येक बूलियन को अलग से लौटाते हैं

  • वस्तुओं को कैश करें, यह जानते हुए कि केवल 4 अलग-अलग उदाहरण थे

मैंने दूसरे को चुना, क्योंकि इसका आवेदन पर कम से कम प्रभाव पड़ा और परिचय करना आसान था। मुझे एक थ्रेड-नॉट-सेफ कैश के साथ एक फैक्ट्री लगाने में मिनटों का समय लगा (मुझे थ्रेड सेफ्टी की जरूरत नहीं थी क्योंकि आखिरकार मेरे पास केवल 4 अलग-अलग इंस्टेंसेस होंगे)।

आवंटन दर 1 जीबी / एस तक नीचे चली गई, और इसलिए युवा जीसी की आवृत्ति (3 से विभाजित) हुई।

उम्मीद है की वो मदद करदे !


11

यदि आपके पास बस मूल्य वस्तुएं हैं (जो कि अन्य वस्तुओं का कोई संदर्भ नहीं है) और वास्तव में, लेकिन मेरा मतलब है कि वास्तव में टन और उनमें से टन, आप ByteBuffersमूल बाइट ऑर्डर के साथ प्रत्यक्ष का उपयोग कर सकते हैं [उत्तरार्द्ध महत्वपूर्ण है] और आपको कुछ सौ लाइनों की आवश्यकता है कोड आवंटित / पुनः प्राप्त करने / पाने वाले / बसने वालों के लिए। गेटर्स के समान दिखते हैंlong getQuantity(int tupleIndex){return buffer.getLong(tupleInex+QUANTITY_OFFSSET);}

यह जीसी समस्या को लगभग पूरी तरह से हल कर देगा जब तक कि आप केवल एक बार आवंटित करते हैं, यानी एक बड़ा हिस्सा और फिर वस्तुओं को स्वयं प्रबंधित करें। संदर्भों के बजाय आपके पास केवल अनुक्रमणिका (जो है int) ByteBufferउस में पास होनी चाहिए। आपको मेमोरी को खुद के साथ संरेखित करने की आवश्यकता हो सकती है।

तकनीक का उपयोग करने की तरह महसूस होता है C and void*, लेकिन कुछ लपेटने के साथ यह सहने योग्य है। यदि कंपाइलर इसे समाप्त करने में विफल रहता है, तो एक प्रदर्शन डाउनसाइड सीमा की जाँच कर सकता है। एक बड़ा उल्टा इलाका है अगर आप वैक्टर जैसे ट्यूपल्स को प्रोसेस करते हैं, तो ऑब्जेक्ट हेडर की कमी मेमोरी फुटप्रिंट को भी कम करती है।

इसके अलावा, यह संभव है कि आपको इस तरह के दृष्टिकोण की आवश्यकता नहीं होगी क्योंकि लगभग सभी जेवीएम की युवा पीढ़ी तुच्छ रूप से मर जाती है और आवंटन लागत केवल एक संकेतक टक्कर है। आवंटन की लागत थोड़ी अधिक हो सकती है यदि आप finalखेतों का उपयोग करते हैं क्योंकि उन्हें कुछ प्लेटफार्मों (जैसे एआरएम / पावर) पर मेमोरी बाड़ की आवश्यकता होती है, x86 पर यह मुफ़्त है, हालांकि।


8

मान लें कि जीसी एक मुद्दा है (जैसा कि अन्य बताते हैं कि यह नहीं हो सकता है) आप विशेष मामले के लिए अपने स्वयं के स्मृति प्रबंधन को लागू करेंगे अर्थात एक वर्ग जो बड़े पैमाने पर मंथन करता है। ऑब्जेक्ट को पूल में जाने दें, मैंने ऐसे मामले देखे हैं जहां यह काफी अच्छी तरह से काम करता है। ऑब्जेक्ट पूल को लागू करना एक अच्छी तरह से ट्रोडेन मार्ग है, इसलिए यहां फिर से जाने की आवश्यकता नहीं है, इसके लिए बाहर देखें:

  • मल्टी-थ्रेडिंग: थ्रेड स्थानीय पूल का उपयोग करना आपके मामले के लिए काम कर सकता है
  • बैकिंग डेटा संरचना: ArrayDeque का उपयोग करने पर विचार करें क्योंकि यह हटाने पर अच्छा प्रदर्शन करता है और इसका कोई आवंटन ओवरहेड नहीं है
  • अपने पूल का आकार सीमित करें :)

आदि से पहले / बाद में मापें


6

मैं एक ऐसी ही समस्या से मिला हूं। सबसे पहले, छोटी वस्तुओं के आकार को कम करने का प्रयास करें। हमने प्रत्येक ऑब्जेक्ट उदाहरण में उन्हें संदर्भित करते हुए कुछ डिफ़ॉल्ट फ़ील्ड मान पेश किए।

उदाहरण के लिए, माउसइवेंट में पॉइंट क्लास का संदर्भ होता है। हमने नए उदाहरण बनाने के बजाय पॉइंट्स को कैश किया और उन्हें संदर्भित किया। उदाहरण के लिए, खाली तार।

एक अन्य स्रोत कई बूलियन थे जिन्हें एक इंट के साथ बदल दिया गया था और प्रत्येक बूलियन के लिए हम इंट के सिर्फ एक बाइट का उपयोग करते हैं।


बस ब्याज से बाहर: यह क्या आप प्रदर्शन बुद्धिमान खरीदा? क्या आपने परिवर्तन से पहले और बाद में अपने आवेदन को प्रोफाइल किया था, और यदि हां, तो परिणाम क्या थे?
एक्सल

@ वस्तुओं को बहुत कम मेमोरी का उपयोग करते हैं इसलिए जीसी को इतनी बार नहीं कहा जाता है। निश्चित रूप से हमने अपने ऐप की रूपरेखा तैयार की लेकिन बेहतर गति का दृश्य प्रभाव भी था।
स्टैनिस्लाव

6

मैंने कुछ समय पहले XML प्रसंस्करण कोड के साथ इस परिदृश्य को निपटाया। मैंने खुद को लाखों XML टैग ऑब्जेक्ट बनाने के लिए पाया जो बहुत छोटे थे (आमतौर पर सिर्फ एक स्ट्रिंग) और बेहद अल्पकालिक (एक की विफलता) XPath चेक मतलब था कोई मैच नहीं तो हारना)।

मैंने कुछ गंभीर परीक्षण किया और इस नतीजे पर पहुंचा कि मैं केवल नए बनाने के बजाय त्याग किए गए टैगों की सूची का उपयोग करके गति पर लगभग 7% सुधार प्राप्त कर सकता हूं। हालाँकि, एक बार कार्यान्वित होने के बाद, मैंने पाया कि मुफ्त कतार के लिए एक तंत्र की जरूरत थी, अगर वह बहुत बड़ा हो जाए - तो इससे मेरा अनुकूलन पूरी तरह से समाप्त हो जाता है इसलिए मैंने इसे एक विकल्प में बदल दिया।

संक्षेप में - शायद इसके लायक नहीं है - लेकिन मुझे यह देखकर खुशी हुई कि आप इसके बारे में सोच रहे हैं, यह आपको परवाह दिखाता है।


2

यह देखते हुए कि आप एक शतरंज कार्यक्रम लिख रहे हैं, कुछ विशेष तकनीकें हैं जिनका उपयोग आप अच्छे प्रदर्शन के लिए कर सकते हैं। एक सरल तरीका यह है कि लोंगो (या बाइट्स) की एक बड़ी सरणी बनाई जाए और इसे स्टैक के रूप में माना जाए। हर बार जब आपका मूव जेनरेटर चलता है तो यह स्टैक पर कुछ नंबरों को धकेलता है, जैसे कि वर्गाकार से चलता है और वर्गाकार में जाता है। जैसा कि आप खोज ट्री का मूल्यांकन करते हैं, आप चालों को पॉपिंग करेंगे और बोर्ड प्रतिनिधित्व को अपडेट करेंगे।

यदि आप अभिव्यंजक शक्ति का उपयोग करना चाहते हैं। यदि आप गति चाहते हैं (इस मामले में) देशी।


1

इस तरह के खोज एल्गोरिदम के लिए मैंने जो एक समाधान का उपयोग किया है, वह सिर्फ एक मूव ऑब्जेक्ट बनाना है, इसे नए मूव के साथ म्यूट करें, और फिर स्कोप छोड़ने से पहले मूव को पूर्ववत करें। आप शायद एक समय में सिर्फ एक चाल का विश्लेषण कर रहे हैं, और फिर बस कहीं सबसे अच्छी चाल का भंडारण कर रहे हैं।

यदि यह किसी कारण से संभव नहीं है, और आप पीक मेमोरी उपयोग को कम करना चाहते हैं, तो मेमोरी दक्षता के बारे में एक अच्छा लेख यहां है: http://www.cs.virginia.edu/kim/publicity/pldi09tutorials/mory-efficient-java-java- tutorial.pdf


डेड लिंक। क्या उस लेख का कोई अन्य स्रोत है?
22

0

बस अपनी लाखों वस्तुएं बनाएं और अपने कोड को उचित तरीके से लिखें: इन वस्तुओं के अनावश्यक संदर्भ न रखें। जीसी आपके लिए गंदा काम करेगा। जैसा कि वे वास्तव में GC'd हैं यह देखने के लिए आप उल्लिखित GC GC के साथ खेल सकते हैं। जावा वस्तुओं को बनाने और जारी करने के बारे में है। :)


1
क्षमा करें दोस्त, मैं आपके दृष्टिकोण से असहमत हूं ... जावा, किसी भी प्रोग्रामिंग भाषा की तरह, यह एक समस्या को हल करने के बारे में है, यह बाधा है, अगर ओपी जीसी द्वारा विवश है तो आप उसकी मदद कैसे कर रहे हैं?
निट्सन वकार्ट

1
मैं उसे बता रहा हूं कि जावा वास्तव में कैसे काम करता है। यदि वह लाखों टेम्प ऑब्जेक्ट्स की स्थिति को चकमा देने में असमर्थ है, तो सबसे अच्छी सलाह यह हो सकती है कि, टेम्प क्लास हल्की होनी चाहिए और उसे यह सुनिश्चित करना चाहिए कि वह जल्द से जल्द संदर्भ जारी करे, एक भी कदम नहीं। क्या मैं कुछ भूल रहा हूँ?
ज्योर्तिग्रहम

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

0

मुझे लगता है कि आपको जावा में स्टैक आवंटन के बारे में पढ़ना चाहिए और विश्लेषण से बचना चाहिए।

क्योंकि यदि आप इस विषय में गहराई से जाते हैं, तो आप पा सकते हैं कि आपकी वस्तुओं को ढेर पर भी आवंटित नहीं किया गया है, और उन्हें जीसी द्वारा एकत्र नहीं किया जाता है जिस तरह से ढेर पर ऑब्जेक्ट हैं।

जावा में यह कैसे काम करता है, उदाहरण के साथ, एस्केप विश्लेषण का विकिपीडिया विवरण है:

http://en.wikipedia.org/wiki/Escape_analysis


0

मैं GC का बहुत बड़ा प्रशंसक नहीं हूं, इसलिए मैं हमेशा इसके आसपास के रास्ते खोजने की कोशिश करता हूं। इस मामले में मैं ऑब्जेक्ट पूल पैटर्न का उपयोग करने का सुझाव दूंगा :

विचार यह है कि नई वस्तुओं को स्टैक में स्टोर करके बनाने से बचें ताकि आप इसे बाद में पुन: उपयोग कर सकें।

Class MyPool
{
   LinkedList<Objects> stack;

   Object getObject(); // takes from stack, if it's empty creates new one
   Object returnObject(); // adds to stack
}

3
छोटी वस्तुओं के लिए पूल का उपयोग करना एक बुरा विचार है, आपको बूट करने के लिए प्रति थ्रेड पूल की आवश्यकता होती है (या साझा पहुंच किसी भी प्रदर्शन को मारता है)। इस तरह के पूल एक अच्छे कचरा संग्राहक से भी बदतर प्रदर्शन करते हैं। अंतिम: GC / w / समवर्ती कोड / संरचनाओं से निपटने के दौरान ईश्वरत्व है - कई एल्गोरिदम को लागू करना काफी आसान है क्योंकि स्वाभाविक रूप से कोई ABA समस्या नहीं है। संदर्भ। समवर्ती वातावरण में गिनती के लिए कम से कम एक परमाणु ऑपरेशन + मेमोरी बाड़ (X86 पर लॉक एडीडी या कैस) की आवश्यकता होती है
'19:

1
कचरा संग्रहकर्ता को चलने देने की तुलना में पूल में वस्तुओं का प्रबंधन अधिक महंगा हो सकता है ।
थोरबजोरन रावन एंडरसन

@ ThorbjørnRavnAndersen आम तौर पर मैं आपसे सहमत हूं, लेकिन ध्यान दें कि इस तरह के अंतर का पता लगाना काफी चुनौतीपूर्ण है, और जब आप इस निष्कर्ष पर पहुंचते हैं कि जीसी आपके मामले में बेहतर काम करता है, तो यह बहुत ही अनूठा मामला होना चाहिए अगर यह अंतर मायने रखता है। कभी आसपास का दूसरा रास्ता, यह संभवतः हो सकता है कि ऑब्जेक्ट पूल आपके ऐप को बचाएगा।
इलैया गज़मैन

1
मैं बस अपने तर्क नहीं मिलता है? यह पता लगाना बहुत मुश्किल है कि क्या जीसी ऑब्जेक्ट पूलिंग से तेज है? और इसलिए आपको ऑब्जेक्ट पूलिंग का उपयोग करना चाहिए? JVM क्लीन कोडिंग और कम समय तक रहने वाली वस्तुओं के लिए अनुकूलित है। यदि वे हैं जो यह सवाल है (जो मुझे आशा है कि अगर ओपी उनमें से एक मिलियन पीआर सेकंड उत्पन्न करता है) तो यह केवल तभी होना चाहिए जब आपके सुझाव के अनुसार अधिक जटिल और त्रुटि प्रवण योजना पर स्विच करने के लिए एक सिद्ध लाभ हो। अगर यह साबित करना बहुत कठिन है, तो परेशान क्यों होना।
थोरबजोरन रावन एंडरसन

0

ऑब्जेक्ट पूल ढेर पर (कुछ समय 10x) सुधार प्रदान करते हैं। लेकिन एक लिंक की गई सूची का उपयोग करके उपरोक्त कार्यान्वयन भोला और गलत दोनों है! लिंक की गई सूची प्रयास को अशक्त करते हुए इसकी आंतरिक संरचना का प्रबंधन करने के लिए ऑब्जेक्ट बनाती है। एक रिंगबफ़र वस्तुओं की एक सरणी का उपयोग करके अच्छी तरह से काम करता है। उदाहरण देने में (एक शतरंज प्रोग्रामम चाल चलता है) रिंगबफ़र को सभी अनिवार्य चालों की सूची के लिए एक धारक ऑब्जेक्ट में लपेटा जाना चाहिए। केवल मूव होल्डर ऑब्जेक्ट रेफरेंस को फिर पास किया जाएगा।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.