আমি কীভাবে সি ++ এ একটি রক্ষণাবেক্ষণযোগ্য, দ্রুত, সংকলন-বিট-মাস্ক লিখব?


113

আমার কাছে এমন কিছু কোড রয়েছে যা কমবেশি এর মতো:

#include <bitset>

enum Flags { A = 1, B = 2, C = 3, D = 5,
             E = 8, F = 13, G = 21, H,
             I, J, K, L, M, N, O };

void apply_known_mask(std::bitset<64> &bits) {
    const Flags important_bits[] = { B, D, E, H, K, M, L, O };
    std::remove_reference<decltype(bits)>::type mask{};
    for (const auto& bit : important_bits) {
        mask.set(bit);
    }

    bits &= mask;
}

বিড়ম্বনা> = 3.6 স্মার্ট জিনিসটি করে এবং এটি একটি একক andনির্দেশায় সংকলন করে (যা অন্যত্র সর্বত্র অন্তর্ভুক্ত হয়):

apply_known_mask(std::bitset<64ul>&):  # @apply_known_mask(std::bitset<64ul>&)
        and     qword ptr [rdi], 775946532
        ret

তবে জিসিসির প্রতিটি সংস্করণ আমি এটি একটি বিরাট গণ্ডগোলের সাথে সংকলন করার চেষ্টা করেছি যার মধ্যে ত্রুটি পরিচালনার অন্তর্ভুক্ত রয়েছে যা স্থিরভাবে DCE'd হওয়া উচিত। অন্য কোডে এটি important_bitsকোডের সাথে সামঞ্জস্য রেখে ডেটা হিসাবে সমান রাখবে!

.LC0:
        .string "bitset::set"
.LC1:
        .string "%s: __position (which is %zu) >= _Nb (which is %zu)"
apply_known_mask(std::bitset<64ul>&):
        sub     rsp, 40
        xor     esi, esi
        mov     ecx, 2
        movabs  rax, 21474836482
        mov     QWORD PTR [rsp], rax
        mov     r8d, 1
        movabs  rax, 94489280520
        mov     QWORD PTR [rsp+8], rax
        movabs  rax, 115964117017
        mov     QWORD PTR [rsp+16], rax
        movabs  rax, 124554051610
        mov     QWORD PTR [rsp+24], rax
        mov     rax, rsp
        jmp     .L2
.L3:
        mov     edx, DWORD PTR [rax]
        mov     rcx, rdx
        cmp     edx, 63
        ja      .L7
.L2:
        mov     rdx, r8
        add     rax, 4
        sal     rdx, cl
        lea     rcx, [rsp+32]
        or      rsi, rdx
        cmp     rax, rcx
        jne     .L3
        and     QWORD PTR [rdi], rsi
        add     rsp, 40
        ret
.L7:
        mov     ecx, 64
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:.LC1
        xor     eax, eax
        call    std::__throw_out_of_range_fmt(char const*, ...)

আমার এই কোডটি কীভাবে লিখব যাতে উভয় সংকলক সঠিক কাজ করতে পারে? এটি ব্যর্থ হয়ে, আমি কীভাবে এটি লিখব যাতে এটি পরিষ্কার, দ্রুত এবং রক্ষণাবেক্ষণযোগ্য থেকে যায়?


4
একটি লুপ ব্যবহার করার পরিবর্তে, আপনি কি একটি মুখোশ দিয়ে তৈরি করতে পারবেন না B | D | E | ... | O?
হলিব্ল্যাকগেট

6
এনামের ইতিমধ্যে বিস্তৃত বিটের চেয়ে বিট পজিশন রয়েছে, তাই আমিও করতে পেরেছি(1ULL << B) | ... | (1ULL << O)
অ্যালেক্স রিঙ্কিং

3
নেতিবাচক দিকটি হ'ল প্রকৃত নামগুলি দীর্ঘ এবং অনিয়মিত এবং এটি যে লাইন শব্দগুলির সাথে মুখোশগুলিতে কোন পতাকাগুলি রয়েছে তা দেখতে প্রায় সহজ নয়।
অ্যালেক্স রিঙ্কিং

4
@ অ্যালেক্সরিকিং আপনি এটি একটি করতে পারেন (1ULL << Constant)| প্রতি লাইন, এবং বিভিন্ন লাইনে ধ্রুবক নামগুলি সারিবদ্ধ করুন, এটি চোখে সহজ হবে।
einpoklum

আমি মনে করি এখানে সমস্যা ব্যবহারের স্বাক্ষরবিহীন টাইপ অভাব এর সাথে সম্পর্কিত, জিসিসি সবসময় স্ট্যাটিক্যালি স্বাক্ষরিত / বিট স্থানান্তর এখান থেকে স্বাক্ষরবিহীন hybrid.Result মধ্যে ওভারফ্লো লিখুন রূপান্তর জন্য সংশোধন খারিজ সঙ্গে যন্ত্রণার একটি হল ছিল intবিট অপারেশন ফলাফল হতে পারে intবা হতে পারে long longমান উপর নির্ভর করে এবং আনুষ্ঠানিকভাবে enumএকটি intধ্রুবকের সমতুল্য নয় । ঝাঁকুনির জন্য "যেনো" কল করা হয়, জিসিসি পেডেন্টিক থাকে
সুইফট - শুক্রবার পাই

উত্তর:


112

সেরা সংস্করণ হয় :

template< unsigned char... indexes >
constexpr unsigned long long mask(){
  return ((1ull<<indexes)|...|0ull);
}

তারপর

void apply_known_mask(std::bitset<64> &bits) {
  constexpr auto m = mask<B,D,E,H,K,M,L,O>();
  bits &= m;
}

ফেরা , আমরা এই অদ্ভুত কৌশলটি করতে পারি:

template< unsigned char... indexes >
constexpr unsigned long long mask(){
  auto r = 0ull;
  using discard_t = int[]; // data never used
  // value never used:
  discard_t discard = {0,(void(
    r |= (1ull << indexes) // side effect, used
  ),0)...};
  (void)discard; // block unused var warnings
  return r;
}

বা, যদি আমরা আটকে থাকি , আমরা এটি পুনরাবৃত্তভাবে সমাধান করতে পারি:

constexpr unsigned long long mask(){
  return 0;
}
template<class...Tail>
constexpr unsigned long long mask(unsigned char b0, Tail...tail){
  return (1ull<<b0) | mask(tail...);
}
template< unsigned char... indexes >
constexpr unsigned long long mask(){
  return mask(indexes...);
}

সমস্ত 3 দিয়ে গডবোল্ট - আপনি সিপিপি_ভিভারশন সংজ্ঞায়িত করতে পারেন এবং অভিন্ন সমাবেশ পেতে পারেন।

অনুশীলনে আমি সবচেয়ে আধুনিক ব্যবহার করতে পারতাম। 14 টি মারছে কারণ আমাদের পুনরাবৃত্তি নেই এবং তাই হে (এন ^ 2) প্রতীক দৈর্ঘ্য (যা সংকলনের সময় এবং সংকলক মেমরির ব্যবহারটি বিস্ফোরিত করতে পারে); 17 বীট 14 মারছে কারণ সংকলকটির সেই অ্যারেটিকে ডেড-কোড-নির্মূল করতে হবে না এবং সেই অ্যারে ট্রিকটি কেবল কুৎসিত।

এর মধ্যে 14 টি সবচেয়ে বিভ্রান্তিকর। এখানে আমরা সমস্ত 0 এর একটি বেনামে অ্যারে তৈরি করি, এর মধ্যে পার্শ্ব প্রতিক্রিয়া হিসাবে আমাদের ফলাফলটি তৈরি করুন, তারপরে অ্যারেটি বাতিল করুন। বাতিল হওয়া অ্যারেটিতে আমাদের প্যাকের আকারের সমান 0 এর একটি সংখ্যা রয়েছে, এবং 1 টি (যা আমরা খালি প্যাকগুলি পরিচালনা করতে পারি তাই যোগ করি)।


কি এর একটি বিশদ ব্যাখ্যা সংস্করণ করছে এটি একটি কৌশল / হ্যাক এবং সি ++ 14 এর দক্ষতার সাথে প্যারামিটার প্যাকগুলি প্রসারণ করতে আপনাকে এটি করতে হবে তা হ'ল যুক্ত কারণগুলিতে ভাঁজ হওয়া যুক্ত কারণগুলির মধ্যে একটি

এটি ভিতরে থেকে বাইরে থেকে ভালভাবে বোঝা যাচ্ছে:

    r |= (1ull << indexes) // side effect, used

এটি কেবলমাত্র একটি নির্দিষ্ট সূচকের rসাথে আপডেট হয় 1<<indexesindexesএকটি প্যারামিটার প্যাক, তাই আমাদের এটি প্রসারিত করতে হবে।

বাকি কাজটি এর indexesভিতরে প্রসারিত করার জন্য একটি প্যারামিটার প্যাক সরবরাহ করা ।

এক ধাপ বাইরে:

(void(
    r |= (1ull << indexes) // side effect, used
  ),0)

এখানে আমরা আমাদের অভিব্যক্তিটি এখানে ফেলেছি void, এটি নির্দেশ করে যে আমরা এর রিটার্ন মানটির বিষয়ে চিন্তা করি না (আমরা কেবল সেটিং এর পার্শ্ব প্রতিক্রিয়া চাই r- সি ++ তে, a |= bতারা যে মানটি সেট aকরে সেগুলি ফেরত দেওয়ার মতো এক্সপ্রেশনও )।

তারপরে আমরা কমা অপারেটরটি ব্যবহার করি ,এবং "মান" 0বাতিল করতে voidএবং মানটি ফেরত দিতে 0। সুতরাং এটি এমন একটি অভিব্যক্তি যা এর মান 0এবং 0এটি গণনার একটি পার্শ্ব প্রতিক্রিয়া হিসাবে এটি কিছুটা সেট করে r

  int discard[] = {0,(void(
    r |= (1ull << indexes) // side effect, used
  ),0)...};

এই মুহুর্তে, আমরা প্যারামিটার প্যাকটি প্রসারিত করি indexes। সুতরাং আমরা পেতে:

 {
    0,
    (expression that sets a bit and returns 0),
    (expression that sets a bit and returns 0),
    [...]
    (expression that sets a bit and returns 0),
  }

মধ্যে {}। এই ব্যবহার ,হয় না কমা অপারেটর, বরং অ্যারের উপাদান বিভাজক। এইsizeof...(indexes)+1 0 s, যা rপার্শ্ব প্রতিক্রিয়া হিসাবে বিট সেট করে । আমরা তারপরে {}অ্যারে নির্মাণের নির্দেশাবলী বরাদ্দ করি discard

পরবর্তী আমরা নিক্ষেপ discardকরতে void- আপনি একটি পরিবর্তনশীল তৈরি সবচেয়ে কম্পাইলার আপনাকে সতর্ক হবে এবং এটি পড়া না। আপনি যদি এটির কাছে ফেলে থাকেন তবে সমস্ত সংকলক অভিযোগ করবেন না void, এটি "হ্যাঁ, আমি জানি, আমি এটি ব্যবহার করছি না" বলার উপায় এটি, তাই এটি সতর্কতা দমন করে।


38
দুঃখিত, তবে যে সি ++ 14 কোড কিছু। আমি কি জানি না।
জেমস

14
@ জামেস এটি কেন একটি দুর্দান্ত উদ্দীপক উদাহরণ যে কেন সি ++ 17 এ ভাঁজ প্রকাশগুলি খুব স্বাগত। এটি এবং অনুরূপ কৌশলগুলি কোনও পুনরাবৃত্তি ছাড়াই "ইনপ্লেস" কোনও প্যাকটি প্রসারিত করার কার্যকর উপায় হিসাবে পরিণত হয় এবং এটির প্রশংসকরা অপ্টিমাইজ করা সহজ বলে মনে করে।
ইয়াক্ক - অ্যাডাম নেভ্রামাউন্ট

4
@ রুবেন মাল্টি লাইন কনটেক্সপ্রিপ 11
ইয়াক - অ্যাডাম নেভ্রামামন্ট

6
আমি নিজেকে সেই সি ++ 14 কোডে চেক করতে দেখছি না। আমি যেহেতু আমার এটি প্রয়োজন সেহেতু আমি সি ++ ১১ টিতে আটকে থাকব, তবে আমি এটি ব্যবহার করতে পারলেও, সি ++ 14 কোডটির এতটা ব্যাখ্যা প্রয়োজন যা আমি করব না। এই মুখোশগুলি সর্বদা সর্বাধিক 32 টি উপাদান হিসাবে লেখা যেতে পারে, তাই আমি ও (এন ^ 2) আচরণ সম্পর্কে উদ্বিগ্ন নই। সর্বোপরি, যদি এন একটি ধ্রুবক দ্বারা আবদ্ধ হয়, তবে এটি সত্যই ও (1)। ;)
অ্যালেক্স রিঙ্কিং

9
যারা ((1ull<<indexes)|...|0ull)এটি বোঝার চেষ্টা করছেন তাদের কাছে এটি "ভাঁজ অভিব্যক্তি" । বিশেষত এটি একটি "বাইনারি রাইট ভাঁজ" এবং এটি হিসাবে স্থান করা উচিত(pack op ... op init)
হেনরিক হ্যানসেন

47

আপনি যে অপ্টিমাইজেশানটির সন্ধান করছেন তা লুপ পিলিং বলে মনে হচ্ছে, যা এটিকে -O3ম্যানুয়ালি সক্ষম করা হয়েছে -fpeel-loops। আমি নিশ্চিত নই কেন এটি লুপ আন্রোলিংয়ের পরিবর্তে লুপ পিলিংয়ের আওতায় চলেছে, তবে সম্ভবত এটি এর অভ্যন্তরে নোনলোকাল নিয়ন্ত্রণ প্রবাহের সাথে একটি লুপটি আনারোল করতে রাজি নয় (সম্ভাব্যভাবে, পরিসীমা চেক থেকে)।

ডিফল্টরূপে, যদিও, জিসিসি সমস্ত পুনরাবৃত্তি ছুলাতে সক্ষম হওয়ায় থামায়, যা দৃশ্যত প্রয়োজনীয়। পরীক্ষামূলকভাবে, পাসিং -O2 -fpeel-loops --param max-peeled-insns=200(ডিফল্ট মান 100) আপনার মূল কোডটি দিয়ে কাজটি সম্পন্ন করে: https://godbolt.org/z/NNWrga


আপনি আশ্চর্যজনক আপনাকে ধন্যবাদ! আমার কোন ধারণা ছিল না এটি জিসিসিতে কনফিগারযোগ্য! যদিও কোনও কারণে -O3 -fpeel-loops --param max-peeled-insns=200ব্যর্থ হয় ... এটি -ftree-slp-vectorizeদৃশ্যত কারণে ।
অ্যালেক্স রিঙ্কিং

এই সমাধানটি x86-64 টার্গেটের মধ্যে সীমাবদ্ধ বলে মনে হচ্ছে। এআরএম এবং এআরএম 64 এর আউটপুটটি এখনও সুন্দর নয়, এটি আবার ওপি-র জন্য সম্পূর্ণ অপ্রাসঙ্গিক হতে পারে।
রিয়েলটাইম

@ রিয়েলটাইম - এটি আসলে কিছুটা প্রাসঙ্গিক। এই ক্ষেত্রে এটি কাজ করে না তা নির্দেশ করার জন্য ধন্যবাদ। খুব হতাশাজনক যে প্ল্যাটফর্ম-নির্দিষ্ট আইআর তে নামার আগে জিসিসি এটি ধরে না। এলএলভিএম আরও কমার আগে এটি অনুকূল করে optim
অ্যালেক্স রিঙ্কিং

10

যদি কেবল সি ++ 11 ব্যবহার করা আবশ্যক (&a)[N]হয় তবে অ্যারে ক্যাপচার করার একটি উপায়। এটি আপনাকে সাহায্যকারী ফাংশনগুলি ব্যবহার না করেই একটি একক পুনরাবৃত্ত ফাংশন লিখতে দেয়:

template <std::size_t N>
constexpr std::uint64_t generate_mask(Flags const (&a)[N], std::size_t i = 0u){
    return i < N ? (1ull << a[i] | generate_mask(a, i + 1u)) : 0ull;
}

এটি একটি নিয়োগ constexpr auto:

void apply_known_mask(std::bitset<64>& bits) {
    constexpr const Flags important_bits[] = { B, D, E, H, K, M, L, O };
    constexpr auto m = generate_mask(important_bits); //< here
    bits &= m;
}

পরীক্ষা

int main() {
    std::bitset<64> b;
    b.flip();
    apply_known_mask(b);
    std::cout << b.to_string() << '\n';
}

আউটপুট

0000000000000000000000000000000000101110010000000000000100100100
//                                ^ ^^^  ^             ^  ^  ^
//                                O MLK  H             E  D  B

সংকলন সময়ে কমপিউটারেবল টুরিংয়ের যে কোনও গণনা করার জন্য সি ++ `এর দক্ষতার প্রশংসা করতে হবে। এটি অবশ্যই আমার মনকে উড়িয়ে দেয় ( <> )।


পরবর্তী সংস্করণগুলির জন্য সি ++ 14 এবং সি ++ 17 ইয়াক্কের উত্তর ইতিমধ্যে আশ্চর্যজনকভাবে এটি .েকে রেখেছে


3
এটি কীভাবে apply_known_maskপ্রকৃতপক্ষে অনুকূলিত হয়?
অ্যালেক্স রিঙ্কিং

2
@ অ্যালেক্সারিংকিং: সমস্ত ভীতিজনক বিট constexpr। এবং তাত্ত্বিকভাবে এটি পর্যাপ্ত নয়, আমরা জানি যে জিসিসি constexprউদ্দিষ্ট হিসাবে মূল্যায়ন করতে যথেষ্ট সক্ষম ।
এমসাল্টার

8

আমি আপনাকে সঠিক EnumSetটাইপ লিখতে উত্সাহিত করব ।

EnumSet<E>C ++ 14 (এর পরে) এর উপর ভিত্তি করে একটি মৌলিক লেখা std::uint64_tতুচ্ছ:

template <typename E>
class EnumSet {
public:
    constexpr EnumSet() = default;

    constexpr EnumSet(std::initializer_list<E> values) {
        for (auto e : values) {
            set(e);
        }
    }

    constexpr bool has(E e) const { return mData & mask(e); }

    constexpr EnumSet& set(E e) { mData |= mask(e); return *this; }

    constexpr EnumSet& unset(E e) { mData &= ~mask(e); return *this; }

    constexpr EnumSet& operator&=(const EnumSet& other) {
        mData &= other.mData;
        return *this;
    }

    constexpr EnumSet& operator|=(const EnumSet& other) {
        mData |= other.mData;
        return *this;
    }

private:
    static constexpr std::uint64_t mask(E e) {
        return std::uint64_t(1) << e;
    }

    std::uint64_t mData = 0;
};

এটি আপনাকে সহজ কোড লিখতে দেয়:

void apply_known_mask(EnumSet<Flags>& flags) {
    static constexpr EnumSet<Flags> IMPORTANT{ B, D, E, H, K, M, L, O };

    flags &= IMPORTANT;
}

সি ++ 11 এ এর ​​জন্য কয়েকটি কনভলিউশন প্রয়োজন, তবে তা এখনও সম্ভব রয়েছে:

template <typename E>
class EnumSet {
public:
    template <E... Values>
    static constexpr EnumSet make() {
        return EnumSet(make_impl(Values...));
    }

    constexpr EnumSet() = default;

    constexpr bool has(E e) const { return mData & mask(e); }

    void set(E e) { mData |= mask(e); }

    void unset(E e) { mData &= ~mask(e); }

    EnumSet& operator&=(const EnumSet& other) {
        mData &= other.mData;
        return *this;
    }

    EnumSet& operator|=(const EnumSet& other) {
        mData |= other.mData;
        return *this;
    }

private:
    static constexpr std::uint64_t mask(E e) {
        return std::uint64_t(1) << e;
    }

    static constexpr std::uint64_t make_impl() { return 0; }

    template <typename... Tail>
    static constexpr std::uint64_t make_impl(E head, Tail... tail) {
        return mask(head) | make_impl(tail...);
    }

    explicit constexpr EnumSet(std::uint64_t data): mData(data) {}

    std::uint64_t mData = 0;
};

এবং সাথে আমন্ত্রণ জানানো হয়:

void apply_known_mask(EnumSet<Flags>& flags) {
    static constexpr EnumSet<Flags> IMPORTANT =
        EnumSet<Flags>::make<B, D, E, H, K, M, L, O>();

    flags &= IMPORTANT;
}

এমনকি জিসিসি তুচ্ছভাবে গডবোল্টে একটি andনির্দেশনা উত্পন্ন করে :-O1

apply_known_mask(EnumSet<Flags>&):
        and     QWORD PTR [rdi], 775946532
        ret

2
ইন C ++ 11 আপনার অনেক constexprকোড আইনগত নয়। মানে, কারও কারও কাছে 2 টি স্টেটমেন্ট আছে! (সি ++ ১১ কনটেক্সপ্রস চুষে নেওয়া হয়েছে)
ইয়াক - অ্যাডাম নেভ্রামামন্ট

@ ইয়াক্ক-অ্যাডামনেভ্র্যামন্ট: আপনি কি বুঝতে পেরেছিলেন যে আমি কোডের দুটি সংস্করণ পোস্ট করেছি , প্রথমটি সি ++ 14 এর পরে এবং দ্বিতীয়টি সি ++ 11 এর জন্য বিশেষভাবে তৈরি? (এর সীমাবদ্ধতার জন্য অ্যাকাউন্টে)
ম্যাথিউ এম।

1
স্টাড :: uint64_t এর পরিবর্তে std :: অন্তর্নিহিত_প্রকারটি ব্যবহার করা আরও ভাল।
জেমস

@ জেমস: আসলে, না মনে রাখবেন যে EnumSet<E>মান Eহিসাবে একটি মান সরাসরি ব্যবহার করে না , পরিবর্তে ব্যবহার করে 1 << e। এটি সম্পূর্ণরূপে একটি ভিন্ন ডোমেন, যা আসলে ক্লাসটিকে এত মূল্যবান করে তোলে => eপরিবর্তে ঘটনাক্রমে সূচকের কোনও সম্ভাবনা নেই 1 << e
ম্যাথিউ এম।

@MatthieuM। হ্যাঁ আপনি ঠিক. আমি এটি আমাদের নিজস্ব বাস্তবায়নের সাথে বিভ্রান্ত করছি যা আপনার মতো similar (1 << ই) ব্যবহারের অসুবিধাটি হ'ল ই যদি অন্তর্নিহিত_প্রকারের আকারের বাইরে চলে যায় তবে সম্ভবত এটি ইউবি, আশা করি একটি সংকলক ত্রুটি।
জেমস

7

সি ++ 11 যেহেতু আপনি ক্লাসিক টিএমপি কৌশলটিও ব্যবহার করতে পারেন:

template<std::uint64_t Flag, std::uint64_t... Flags>
struct bitmask
{
    static constexpr std::uint64_t mask = 
        bitmask<Flag>::value | bitmask<Flags...>::value;
};

template<std::uint64_t Flag>
struct bitmask<Flag>
{
    static constexpr std::uint64_t value = (uint64_t)1 << Flag;
};

void apply_known_mask(std::bitset<64> &bits) 
{
    constexpr auto mask = bitmask<B, D, E, H, K, M, L, O>::value;
    bits &= mask;
}

সংকলক এক্সপ্লোরারটির লিঙ্ক: https://godbolt.org/z/Gk6KX1

টেমপ্লেট কনটেক্সারপ ফাংশনের মাধ্যমে এই পদ্ধতির সুবিধাটি হ'ল চিলের নিয়মের কারণে সংকলন করা এটি সম্ভবত সামান্য দ্রুত ।


1

এখানে কিছু 'চালাক' ধারণা রয়েছে। আপনি সম্ভবত তাদের অনুসরণ করে রক্ষণাবেক্ষণে সহায়তা করছেন না।

হয়

{B, D, E, H, K, M, L, O};

লেখার চেয়ে অনেক সহজ

(B| D| E| H| K| M| L| O);

?

তারপরে বাকি কোডগুলির কোনওটিরই প্রয়োজন নেই।


1
"বি", "ডি" ইত্যাদি নিজেরাই পতাকা নয়।
মাইচা Łoś

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