क्या मैं एक पूर्णांक की सीमा देकर अनुकूलक का संकेत दे सकता हूँ?


173

मैं एक intप्रकार का उपयोग कर रहा हूं एक मान संग्रहीत करने के लिए। कार्यक्रम के शब्दार्थ द्वारा, मान हमेशा एक बहुत छोटी श्रेणी (0 - 36) में भिन्न होता है, और int(ए char) केवल सीपीयू दक्षता के कारण उपयोग किया जाता है।

ऐसा लगता है कि पूर्णांक की इतनी छोटी श्रृंखला पर कई विशेष अंकगणितीय अनुकूलन किए जा सकते हैं। उन पूर्णांकों पर कई फ़ंक्शन कॉल को "जादुई" संचालन के एक छोटे सेट में अनुकूलित किया जा सकता है, और कुछ फ़ंक्शन टेबल लुक-अप में भी अनुकूलित किए जा सकते हैं।

तो, क्या यह संकलक को बताना संभव है कि यह intहमेशा उस छोटी सी सीमा में है, और क्या संकलक के लिए उन अनुकूलन करना संभव है?


4
मूल्य सीमा अनुकूलन कई संकलकों में मौजूद है, उदाहरण के लिए। llvm लेकिन मैं इसे घोषित करने के लिए किसी भी भाषा संकेत के बारे में पता नहीं हूँ।
रेमस रूसु

2
ध्यान दें कि यदि आपके पास कभी ऋणात्मक संख्याएँ नहीं हैं, तो आपके पास unsignedप्रकारों का उपयोग करने के लिए छोटे लाभ हो सकते हैं क्योंकि वे संकलक के लिए तर्क के लिए आसान होते हैं।
user694733

4
@RemusRusanu: पास्कल आपको उप-प्रकार को परिभाषित करने की अनुमति देता है , जैसे var value: 0..36;
एडगर बोनट

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

1
यह कहना भूल गया: intऔर unsigned int64-बिट पॉइंटर्स वाले अधिकांश सिस्टम पर 32 से 64-बिट तक साइन-या शून्य-विस्तारित होने की आवश्यकता है। ध्यान दें कि x86-64 पर, 32-बिट रजिस्टरों पर परिचालन शून्य-64 के लिए नि: शुल्क -विस्तार करता है (साइन एक्सटेंशन नहीं है, लेकिन हस्ताक्षरित अतिप्रवाह अपरिभाषित व्यवहार है, इसलिए कंपाइलर 64-बिट हस्ताक्षरित गणित का उपयोग कर सकता है यदि वह चाहता है)। तो आप केवल 32-बिट फ़ंक्शन आर्ग को शून्य-विस्तारित करने के लिए अतिरिक्त निर्देश देखते हैं, गणना के परिणाम नहीं। आप संकीर्ण अहस्ताक्षरित प्रकारों के लिए चाहेंगे।
पीटर कॉर्डेस

जवाबों:


230

हाँ यह संभव है। उदाहरण के लिए, gccआप __builtin_unreachableसंकलक को असंभव परिस्थितियों के बारे में बताने के लिए उपयोग कर सकते हैं, जैसे:

if (value < 0 || value > 36) __builtin_unreachable();

हम ऊपर की स्थिति को एक मैक्रो में लपेट सकते हैं:

#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)

और इसका इस्तेमाल ऐसे करें:

assume(x >= 0 && x <= 10);

जैसा कि आप देख सकते हैं , gccइस जानकारी के आधार पर अनुकूलन करते हैं:

#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)

int func(int x){
    assume(x >=0 && x <= 10);

    if (x > 11){
        return 2;
    }
    else{
        return 17;
    }
}

पैदा करता है:

func(int):
    mov     eax, 17
    ret

हालांकि, एक नकारात्मक पक्ष यह है कि यदि आपका कोड कभी भी ऐसी धारणाओं को तोड़ता है, तो आपको अपरिभाषित व्यवहार मिलता है

डिबग बिल्ड में भी यह आपको सूचित नहीं करता है। डीबग / टेस्ट / बग्स को मान्यताओं के साथ और अधिक आसानी से पकड़ने के लिए, आप हाइब्रिड मान / एस्टर मैक्रो का उपयोग कर सकते हैं (@ जेडवीडी को क्रेडिट), जैसे यह:

#if defined(NDEBUG)
#define assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)
#else
#include <cassert>
#define assume(cond) assert(cond)
#endif

डिबग बिल्ड में ( परिभाषित NDEBUG नहीं ) के साथ, यह एक साधारण assert, प्रिंटिंग एरर मैसेज और abort'आईएनजी प्रोग्राम की तरह काम करता है , और रिलीज बिल्ड में यह एक धारणा का उपयोग करता है, अनुकूलित कोड का निर्माण करता है।

ध्यान दें, हालांकि, यह नियमित के लिए एक विकल्प नहीं है assert- condरिलीज बिल्ड में रहता है, इसलिए आपको ऐसा कुछ नहीं करना चाहिए assume(VeryExpensiveComputation())


5
@Xofo, यह नहीं मिला, मेरे उदाहरण में यह पहले से ही हो रहा है, क्योंकि return 2संकलक द्वारा कोड से शाखा को हटा दिया गया है।

6
हालाँकि, ऐसा लगता है कि जीसी जादुई कार्यों या टेबल लुकअप के कार्यों को ऑप्टिमाइज़ नहीं कर सकता है जैसा कि ओपी को उम्मीद थी।
jingyu9575

19
@ user3528438, __builtin_expectएक गैर-सख्त संकेत है। __builtin_expect(e, c)"के रूप में eमूल्यांकन करने के लिए सबसे अधिक संभावना है " पढ़ना चाहिए c, और शाखा भविष्यवाणी को अनुकूलित करने के लिए उपयोगी हो सकता है, लेकिन यह eहमेशा के लिए प्रतिबंधित नहीं होता है c, इसलिए अनुकूलक को अन्य मामलों को दूर करने की अनुमति नहीं देता है। देखें कि विधानसभा में शाखाएं कैसे आयोजित की जाती हैं

6
सिद्धांत रूप में कोई भी कोड जो बिना शर्त के अपरिभाषित व्यवहार का कारण बनता है, का उपयोग किया जा सकता है __builtin_unreachable()
कोडइन्चोअस

14
जब तक कुछ क्वर्की नहीं होता है, जिसके बारे में मुझे पता नहीं है कि यह एक बुरा विचार है, तो यह इस के साथ संयोजन करने के लिए समझ में आता है assert, उदाहरण के लिए परिभाषित assumeकिया गया है assertजब NDEBUGपरिभाषित नहीं किया जाता है, और __builtin_unreachable()जब NDEBUGपरिभाषित किया जाता है। इस तरह आपको उत्पादन कोड में धारणा का लाभ मिलता है, लेकिन डिबग बिल्ड में आपके पास अभी भी एक स्पष्ट जांच है। बेशक आपको फिर खुद को आश्वस्त करने के लिए पर्याप्त परीक्षण करना होगा कि अनुमान जंगली में संतुष्ट हो जाएगा।
डेविड जेड

61

इसके लिए मानक समर्थन है। आपको क्या करना चाहिए stdint.h( cstdint) और फिर प्रकार का उपयोग करना शामिल है uint_fast8_t

यह संकलक को बताता है कि आप केवल 0 - 255 के बीच की संख्या का उपयोग कर रहे हैं, लेकिन अगर यह तेज कोड देता है तो यह एक बड़े प्रकार का उपयोग करने के लिए स्वतंत्र है। इसी तरह, कंपाइलर मान सकता है कि वेरिएबल की वैल्यू 255 से ऊपर नहीं होगी और फिर उसके अनुसार ऑप्टिमाइजेशन करेंगे।


2
इन प्रकारों का उपयोग लगभग नहीं किया जाता है जितना उन्हें होना चाहिए (मैं व्यक्तिगत रूप से भूल जाता हूं कि वे मौजूद हैं)। वे कोड देते हैं जो तेज और पोर्टेबल, बहुत शानदार है। और वे 1999 के बाद से रहे हैं।
लुंडिन

सामान्य मामले के लिए यह एक अच्छा सुझाव है। इनकार का जवाब विशिष्ट परिदृश्यों के लिए अधिक निंदनीय समाधान दिखाता है।
ऑर्बिट

1
कंपाइलर केवल सिस्टम पर 0-255 श्रेणी की जानकारी प्राप्त करता है जहां uint_fast8_tवास्तव में 8-बिट प्रकार (जैसे unsigned char) है जैसे यह x86 / ARM / MIPS / PPC ( godbolt.org/g/KNyc31 ) पर है। 21164A से पहले डीईसी अल्फा पर , बाइट लोड / स्टोर समर्थित नहीं थे, इसलिए कोई भी कार्यान्वयन लागू होगा typedef uint32_t uint_fast8_t। AFAIK, अधिकांश कंपाइलरों (जैसे gcc) के साथ अतिरिक्त रेंज-लिमिट्स के लिए एक प्रकार का कोई तंत्र नहीं है, इसलिए मुझे पूरा यकीन है कि उस मामले में या uint_fast8_tजैसा unsigned intभी हो, ठीक वैसा ही व्यवहार करेंगे ।
पीटर कॉर्डेस

( boolविशेष है, और 0 या 1 के लिए सीमा-सीमित है, लेकिन यह एक अंतर्निहित प्रकार है, जिसे chargcc / clang के संदर्भ में हेडर फ़ाइलों द्वारा परिभाषित नहीं किया गया है । जैसे मैंने कहा, मुझे नहीं लगता कि अधिकांश कंपाइलरों में एक तंत्र है। ऐसा संभव होगा।)
पीटर कॉर्ड्स

1
वैसे भी, uint_fast8_tएक अच्छी सिफारिश है, क्योंकि यह प्लेटफार्मों पर 8-बिट प्रकार का उपयोग करेगा जहां यह उतना ही कुशल है unsigned int। (मैं वास्तव में निश्चित नहीं हूं कि किस fastप्रकार के लिए तेज़ होना चाहिए , और क्या कैश फ़ुटप्रिंट ट्रेडऑफ़ इसका हिस्सा होना चाहिए।) x86 को बाइट संचालन के लिए व्यापक समर्थन है, यहां तक ​​कि एक मेमोरी स्रोत के साथ बाइट ऐड करने के लिए, इसलिए आपको एक अलग शून्य-लोडिंग लोड (जो बहुत सस्ता भी है) करने की आवश्यकता नहीं है। gcc uint_fast16_tx86 पर 64-बिट प्रकार बनाता है, जो कि अधिकांश उपयोगों (बनाम 32-बिट) के लिए पागल है। godbolt.org/g/Rmq5bv
पीटर कॉर्डेस

8

वर्तमान उत्तर उस मामले के लिए अच्छा है जब आप यह जानते हैं कि सीमा क्या है, लेकिन यदि आप अभी भी सही व्यवहार चाहते हैं, जब मूल्य अपेक्षित सीमा से बाहर है, तो यह काम नहीं करेगा।

उस स्थिति के लिए, मैंने पाया कि यह तकनीक काम कर सकती है:

if (x == c)  // assume c is a constant
{
    foo(x);
}
else
{
    foo(x);
}

विचार एक कोड-डेटा ट्रेडऑफ़ है: आप नियंत्रण तर्क में 1 बिट डेटा (चाहे x == c) स्थानांतरित कर रहे हैं । यह आशावादी के लिए संकेत है जो वास्तव में एक ज्ञात स्थिरांक है , यह इनलाइन को प्रोत्साहित करता है और बाकी से अलग से पहले आह्वान को अनुकूलित करता है , संभवतः काफी भारी है।
xcfoo

कोड को वास्तव में एकल सबरूटीन में बदलना सुनिश्चित करें foo, हालांकि - कोड की नकल न करें।

उदाहरण:

इस तकनीक को काम करने के लिए आपको थोड़ा भाग्यशाली होने की आवश्यकता है - ऐसे मामले हैं जहां संकलक चीजों का सांख्यिकीय रूप से मूल्यांकन नहीं करने का निर्णय लेते हैं, और वे तरह-तरह की मनमानी करते हैं। लेकिन जब यह काम करता है, तो यह अच्छी तरह से काम करता है:

#include <math.h>
#include <stdio.h>

unsigned foo(unsigned x)
{
    return x * (x + 1);
}

unsigned bar(unsigned x) { return foo(x + 1) + foo(2 * x); }

int main()
{
    unsigned x;
    scanf("%u", &x);
    unsigned r;
    if (x == 1)
    {
        r = bar(bar(x));
    }
    else if (x == 0)
    {
        r = bar(bar(x));
    }
    else
    {
        r = bar(x + 1);
    }
    printf("%#x\n", r);
}

बस -O3पूर्व-निर्धारित स्थिरांक 0x20और कोडांतरक आउटपुट0x30e में उपयोग और नोटिस करें ।


क्या आप नहीं चाहेंगे if (x==c) foo(c) else foo(x)? यदि केवल constexprकार्यान्वयन को पकड़ने के लिए foo?
एमएसएल

@MSalters: मुझे पता था कि कोई पूछने वाला था !! मैं इस तकनीक के साथ आया constexprथा पहले एक बात थी और बाद में इसे "अपडेट" करने की जहमत नहीं उठाई (हालांकि मैं वास्तव में कभी constexprभी इसके बारे में चिंता करने के लिए परेशान नहीं हुआ), लेकिन इसका कारण यह है कि मैंने शुरू में ऐसा नहीं किया था, जो मैं चाहता था कंपाइलर के लिए उन्हें कॉमन कोड के रूप में समझना आसान बना देता है और ब्रांच को हटा देता है अगर उसने उन्हें सामान्य विधि कॉल के रूप में छोड़ने का फैसला किया है और अनुकूलन नहीं किया है। मुझे उम्मीद थी कि अगर मैं cसंकलक के लिए सी (माफ करना, बुरा मजाक) के लिए वास्तव में कठिन हूं कि दोनों एक ही कोड हैं, हालांकि मैंने इसे कभी सत्यापित नहीं किया।
user541686

4

मैं सिर्फ यह कहने के लिए पिच कर रहा हूं कि यदि आप एक समाधान चाहते हैं जो अधिक मानक C ++ है, तो आप [[noreturn]]अपनी खुद की लिखने के लिए विशेषता का उपयोग कर सकते हैं unreachable

इसलिए मैं पुनः प्रदर्शित करने के लिए ' उत्कृष्ट उदाहरण ' को पुनः प्रयोग करूँगा :

namespace detail {
    [[noreturn]] void unreachable(){}
}

#define assume(cond) do { if (!(cond)) detail::unreachable(); } while (0)

int func(int x){
    assume(x >=0 && x <= 10);

    if (x > 11){
        return 2;
    }
    else{
        return 17;
    }
}

कौन सा आप देख सकते हैं के रूप में , लगभग समान कोड में परिणाम:

detail::unreachable():
        rep ret
func(int):
        movl    $17, %eax
        ret

नकारात्मक पक्ष निश्चित रूप से है, कि आपको एक चेतावनी मिलती है जो एक [[noreturn]]फ़ंक्शन करता है, वास्तव में, वापस।


यह तब काम करता है clang, जब मेरा मूल समाधान नहीं होता है , इसलिए अच्छी चाल और +1। लेकिन पूरी बात बहुत संकलक-निर्भर है (जैसा कि पीटर कॉर्ड्स ने हमें दिखाया, iccइसमें प्रदर्शन खराब हो सकता है ), इसलिए यह अभी भी सार्वभौमिक रूप से लागू नहीं है। इसके अलावा, लघु नोट: unreachableपरिभाषा को इसके लिए ऑप्टिमाइज़र और इनलाइन करने के लिए उपलब्ध होना चाहिए
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.