Разница между объявлением переменных до или в цикле?


312

Я всегда задавался вопросом, имеет ли вообще какое-либо значение (производительность) объявление общей переменной перед циклом, в отличие от повторяющихся внутри цикла? Пример (довольно бессмысленный) в Java:

а) объявление перед циклом:

double intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

б) объявление (повторно) внутри цикла:

for(int i=0; i < 1000; i++){
    double intermediateResult = i;
    System.out.println(intermediateResult);
}

Какой из них лучше, а или б ?

Я подозреваю, что повторное объявление переменных (пример b ) теоретически создает дополнительные издержки , но компиляторы достаточно умны, чтобы это не имело значения. Преимущество примера b состоит в том, что он более компактен и ограничивает область действия переменной тем местом, где она используется. Тем не менее, я склонен кодировать в соответствии с примером а .

Изменить: я особенно заинтересован в случае Java.


Это важно при написании кода Java для платформы Android. Google предлагает, чтобы для критичного ко времени кода объявлять инкрементные переменные вне цикла for, как будто внутри цикла for, он повторно объявляет его каждый раз в этой среде. Разница в производительности очень заметна для дорогих алгоритмов.
AaronCarson

1
@AaronCarson Не могли бы вы предоставить ссылку на это предложение от Google
Виталий Зинченко

Ответы:


256

Что лучше, а или б ?

С точки зрения производительности, вам придется его измерять. (И, на мой взгляд, если вы можете измерить разницу, компилятор не очень хорош).

С точки зрения обслуживания, b лучше. Объявите и инициализируйте переменные в одном и том же месте, в самой узкой области видимости. Не оставляйте зазор между объявлением и инициализацией и не загрязняйте пространства имен, которые вам не нужны.


5
Вместо Double, если он имеет дело со String, все-таки лучше использовать регистр "b"?
Antoops

3
@ Antoops - да, b лучше по причинам, которые не имеют ничего общего с типом данных объявленной переменной. Почему это было бы по-другому для строк?
Даниэль Уорвикер

215

Ну, я запускал ваши примеры A и B по 20 раз каждый, повторяя 100 миллионов раз (JVM - 1.5.0).

A: среднее время выполнения: 0,074 сек.

B: среднее время выполнения: 0,067 сек.

К моему удивлению, Б был немного быстрее. С такой скоростью, как компьютеры, сейчас трудно сказать, сможете ли вы точно измерить это. Я бы тоже написал код A, но я бы сказал, что это не имеет значения.


12
Вы победили меня, я как раз собирался опубликовать свои результаты для профилирования, я получил более или менее то же самое, и да, на удивление, Б быстрее, действительно, подумал бы А, если бы мне пришлось сделать ставку на это.
Марк Дэвидсон

14
Не удивительно - когда переменная является локальной для цикла, ее не нужно сохранять после каждой итерации, чтобы она могла оставаться в регистре.

142
+1 за тестирование , а не просто мнение / теорию, которую ОП мог бы придумать сам.
MGOwen

3
@ GoodPerson, если честно, я бы хотел, чтобы это было сделано. Я провел этот тест около 10 раз на своей машине для 50 000 000-100 000 000 итераций с почти идентичным фрагментом кода (которым я хотел бы поделиться со всеми, кто хочет запустить статистику). Ответы были разделены почти одинаково в любом случае, как правило, с запасом 900 мс (более 50 млн итераций), что не очень много. Хотя моя первая мысль была о том, что это будет «шум», он может немного понизиться. Хотя эти усилия кажутся мне чисто академическими (для большинства реальных приложений). В любом случае, я бы хотел увидеть результат;) Кто-нибудь согласен?
javatarz

3
Показывать результаты теста без документирования настроек, ничего не стоит Это особенно верно в этом случае, когда оба фрагмента кода выдают идентичный байт-код, поэтому любая измеренная разница является просто признаком недостаточных условий тестирования.
Хольгер

66

Это зависит от языка и точного использования. Например, в C # 1 это не имеет значения. В C # 2, если локальная переменная захвачена анонимным методом (или лямбда-выражением в C # 3), это может иметь очень существенное значение.

Пример:

using System;
using System.Collections.Generic;

class Test
{
    static void Main()
    {
        List<Action> actions = new List<Action>();

        int outer;
        for (int i=0; i < 10; i++)
        {
            outer = i;
            int inner = i;
            actions.Add(() => Console.WriteLine("Inner={0}, Outer={1}", inner, outer));
        }

        foreach (Action action in actions)
        {
            action();
        }
    }
}

Вывод:

Inner=0, Outer=9
Inner=1, Outer=9
Inner=2, Outer=9
Inner=3, Outer=9
Inner=4, Outer=9
Inner=5, Outer=9
Inner=6, Outer=9
Inner=7, Outer=9
Inner=8, Outer=9
Inner=9, Outer=9

Разница в том, что все действия захватывают одну и ту же outerпеременную, но у каждого своя отдельная innerпеременная.


3
в примере B (оригинальный вопрос), действительно ли он каждый раз создает новую переменную? что происходит в глазах стека?
Рой Намир

@ Джон, это была ошибка в C # 1.0? В идеале не должно Outerбыть 9?
nawfal

@nawfal: я не знаю, что ты имеешь в виду. Лямбда-выражений не было в 1.0 ... а Outer равно 9. Какую ошибку вы имеете в виду?
Джон Скит

@nawfal: Моя точка зрения заключается в том, что в C # 1.0 не было языковых возможностей, в которых можно было бы определить разницу между объявлением переменной внутри цикла и объявлением ее вне (при условии, что оба скомпилированы). Это изменилось в C # 2.0. Нет ошибок
Джон Скит

@JonSkeet О, да, я вас понял, я полностью упустил из виду тот факт, что вы не можете закрыть переменные, как это в 1.0, мой плохой! :)
nawfal

35

Вот что я написал и скомпилировал в .NET.

double r0;
for (int i = 0; i < 1000; i++) {
    r0 = i*i;
    Console.WriteLine(r0);
}

for (int j = 0; j < 1000; j++) {
    double r1 = j*j;
    Console.WriteLine(r1);
}

Это то, что я получаю от .NET Reflector, когда CIL возвращается в код.

for (int i = 0; i < 0x3e8; i++)
{
    double r0 = i * i;
    Console.WriteLine(r0);
}
for (int j = 0; j < 0x3e8; j++)
{
    double r1 = j * j;
    Console.WriteLine(r1);
}

Так что после компиляции оба выглядят одинаково. В управляемых языках код преобразуется в CL / байт-код, а во время исполнения он преобразуется в машинный язык. Таким образом, на машинном языке двойка может даже не быть создана в стеке. Это может быть просто регистр, поскольку код отражает, что это временная переменная для WriteLineфункции. Существует целый набор правил оптимизации только для циклов. Так что средний парень не должен беспокоиться об этом, особенно в управляемых языках. Есть случаи, когда вы можете оптимизировать управление кодом, например, если вам нужно объединить большое количество строк, используя только string a; a+=anotherstring[i]vs, используяStringBuilder, Существует очень большая разница в производительности между ними. Есть много таких случаев, когда компилятор не может оптимизировать ваш код, потому что он не может понять, что задумано в большей области. Но это может в значительной степени оптимизировать основные вещи для вас.


int j = 0 for (; j <0x3e8; j ++) таким образом объявляется один раз раз как переменная, а не каждая за цикл. 2) Назначение это более толстый, чем все другие варианты. 3) Таким образом, правило bestpractice - это любое объявление вне итерации для.
Лука

24

Это гоча в VB.NET. Результат Visual Basic не будет повторно инициализировать переменную в этом примере:

For i as Integer = 1 to 100
    Dim j as Integer
    Console.WriteLine(j)
    j = i
Next

' Output: 0 1 2 3 4...

Это будет печатать 0 в первый раз (переменные Visual Basic имеют значения по умолчанию при объявлении!), Но iкаждый раз после этого.

Если вы добавите = 0, однако, вы получите то, что ожидаете:

For i as Integer = 1 to 100
    Dim j as Integer = 0
    Console.WriteLine(j)
    j = i
Next

'Output: 0 0 0 0 0...

1
Я использую VB.NET в течение многих лет и не сталкивался с этим !!
ChrisA

12
Да, это неприятно на практике.
Майкл Харен

Вот ссылка на это от Пола Вика: panopticoncentral.net/archive/2006/03/28/11552.aspx
ferventcoder

1
@eschneider @ferventcoder К сожалению, @PaulV решил оставить свои старые посты в блоге , так что теперь это неработающая ссылка.
Марк Херд

да, только недавно столкнулся с этим; искал некоторые официальные документы по этому вопросу ...
Эрик Шнайдер

15

Я сделал простой тест:

int b;
for (int i = 0; i < 10; i++) {
    b = i;
}

против

for (int i = 0; i < 10; i++) {
    int b = i;
}

Я скомпилировал эти коды с помощью gcc - 5.2.0. А затем я разобрал main () этих двух кодов, и вот результат:

1º:

   0x00000000004004b6 <+0>:     push   rbp
   0x00000000004004b7 <+1>:     mov    rbp,rsp
   0x00000000004004ba <+4>:     mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret

против

   0x00000000004004b6 <+0>: push   rbp
   0x00000000004004b7 <+1>: mov    rbp,rsp
   0x00000000004004ba <+4>: mov    DWORD PTR [rbp-0x4],0x0
   0x00000000004004c1 <+11>:    jmp    0x4004cd <main+23>
   0x00000000004004c3 <+13>:    mov    eax,DWORD PTR [rbp-0x4]
   0x00000000004004c6 <+16>:    mov    DWORD PTR [rbp-0x8],eax
   0x00000000004004c9 <+19>:    add    DWORD PTR [rbp-0x4],0x1
   0x00000000004004cd <+23>:    cmp    DWORD PTR [rbp-0x4],0x9
   0x00000000004004d1 <+27>:    jle    0x4004c3 <main+13>
   0x00000000004004d3 <+29>:    mov    eax,0x0
   0x00000000004004d8 <+34>:    pop    rbp
   0x00000000004004d9 <+35>:    ret 

Которые точно так же, как результат. разве это не доказательство того, что два кода производят одно и то же?


3
да, и это здорово, что вы сделали это, но это возвращается к тому, что люди говорили о зависимости языка / компилятора. Интересно, как это повлияет на производительность JIT или интерпретируемого языка?
user137717

12

Это зависит от языка - IIRC C # оптимизирует это, так что нет никакой разницы, но JavaScript (например) будет каждый раз выполнять весь процесс выделения памяти.


Да, но это не много значит. Я провел простой тест с циклом for, который выполнялся 100 миллионов раз, и обнаружил, что наибольшая разница в пользу объявления вне цикла составляет 8 мс. Обычно это было больше как 3-4, и время от времени объявление вне цикла выполнялось WORSE (до 4 мс), но это не было типичным.
user137717

11

Я бы всегда использовал A (а не полагался на компилятор) и мог бы также переписать:

for(int i=0, double intermediateResult=0; i<1000; i++){
    intermediateResult = i;
    System.out.println(intermediateResult);
}

Это все еще ограничивается intermediateResultобластью действия цикла, но не повторяется во время каждой итерации.


12
Концептуально вы хотите, чтобы переменная существовала в течение цикла, а не отдельно для каждой итерации? Я редко делаю. Напишите код, который раскрывает ваше намерение настолько четко, насколько это возможно, если только у вас нет очень, очень веской причины поступать иначе.
Джон Скит

4
Ах, хороший компромисс, я никогда не думал об этом! ИМО, хотя код становится немного менее визуально «четким», хотя)
Рабарберски

2
@ Джон - я понятия не имею, что на самом деле делает ОП с промежуточным значением. Просто подумал, что это вариант стоит рассмотреть.
Триптих

6

На мой взгляд, b - лучшая структура. В последнем значении middleResult остается после завершения цикла.

Изменить: Это не имеет большого значения для типов значений, но ссылочные типы могут быть несколько весомыми. Лично мне нравится, что переменные разыменовываются как можно скорее для очистки, и b делает это для вас,


sticks around after your loop is finished- хотя это не имеет значения в языке, подобном Python, где связанные имена держатся до конца функции.
new123456

@ new123456: OP запросил специфику Java, даже если вопрос был задан несколько обобщенно. Многие производные от C языки имеют область видимости на уровне блоков: C, C ++, Perl (с myключевым словом), C # и Java для названия 5, которое я использовал.
Powerlord

Я знаю - это было наблюдение, а не критика.
new123456

5

Я подозреваю, что некоторые компиляторы могут оптимизировать оба кода, чтобы они были одним и тем же кодом, но, конечно, не всем. Так что я бы сказал, что вам лучше с первым. Единственная причина последнего - если вы хотите убедиться, что объявленная переменная используется только в вашем цикле.


5

Как правило, я объявляю свои переменные в самой внутренней области видимости. Итак, если вы не используете промежуточный результат вне цикла, то я бы пошел с B.


5

Сотрудник предпочитает первую форму, говоря, что это оптимизация, предпочитая повторно использовать декларацию.

Я предпочитаю второй (и пытаюсь убедить моего сотрудника! ;-)), прочитав это:

  • Это уменьшает область видимости переменных там, где они нужны, и это хорошо.
  • Java оптимизирует достаточно, чтобы не иметь существенного различия в производительности. IIRC, возможно, вторая форма еще быстрее.

В любом случае, это относится к категории преждевременной оптимизации, которая зависит от качества компилятора и / или JVM.


5

Существует разница в C #, если вы используете переменную в лямбде и т. Д. Но в целом компилятор будет делать то же самое, предполагая, что переменная используется только внутри цикла.

Учитывая, что они в основном одинаковы: обратите внимание, что версия b делает читателей гораздо более очевидным, что переменная не используется и не может использоваться после цикла. Кроме того, версия b гораздо легче реорганизована. В версии a сложнее извлечь тело цикла в его собственный метод.Более того, версия b уверяет вас, что такой рефакторинг не имеет побочных эффектов.

Следовательно, версия a бесит меня бесконечно, потому что в этом нет никакой пользы, и это значительно усложняет анализ кода ...


5

Ну, вы всегда можете сделать это для:

{ //Or if(true) if the language doesn't support making scopes like this
    double intermediateResult;
    for (int i=0; i<1000; i++) {
        intermediateResult = i;
        System.out.println(intermediateResult);
    }
}

Таким образом, вы объявляете переменную только один раз, и она умрет, когда вы выйдете из цикла.


4

Я всегда думал, что если вы объявляете свои переменные внутри цикла, то вы тратите впустую память. Если у вас есть что-то вроде этого:

for(;;) {
  Object o = new Object();
}

Тогда не только объект должен быть создан для каждой итерации, но также должна быть новая ссылка, выделенная для каждого объекта. Кажется, что если сборщик мусора работает медленно, у вас будет куча свисающих ссылок, которые необходимо очистить.

Однако, если у вас есть это:

Object o;
for(;;) {
  o = new Object();
}

Затем вы создаете только одну ссылку и каждый раз назначаете ей новый объект. Конечно, это может занять немного больше времени, чтобы выйти из области видимости, но тогда есть только одна свисающая ссылка, чтобы иметь дело с.


3
Новая ссылка не выделяется для каждого объекта, даже если ссылка объявлена ​​в цикле for. В обоих случаях: 1) 'o' - локальная переменная, и место в стеке выделяется для нее один раз в начале функции. 2) В каждой итерации создается новый объект. Так что нет разницы в производительности. Для организации кода, удобочитаемости и удобства сопровождения лучше объявить ссылку в цикле.
Ajoy Bhatia

1
Хотя я не могу говорить за Java, в .NET ссылка не «выделяется» для каждого объекта в первом примере. В этой локальной (для метода) переменной есть одна запись в стеке. Для ваших примеров созданный IL идентичен.
Джесси С. Slicer

3

Я думаю, что это зависит от компилятора и трудно дать общий ответ.


3

Моя практика следующая:

  • если тип переменной прост (int, double, ...), я предпочитаю вариант b (внутри).
    Причина: уменьшение области видимости переменной.

  • если тип переменной не простой (какой-то тип classили struct), я предпочитаю вариант а (снаружи).
    Причина: уменьшение количества вызовов ctor-dtor.


1

С точки зрения производительности, снаружи (намного) лучше.

public static void outside() {
    double intermediateResult;
    for(int i=0; i < Integer.MAX_VALUE; i++){
        intermediateResult = i;
    }
}

public static void inside() {
    for(int i=0; i < Integer.MAX_VALUE; i++){
        double intermediateResult = i;
    }
}

Я выполнил обе функции по 1 млрд раз каждая. outside () заняло 65 миллисекунд. inside () заняло 1,5 секунды.


2
Должна ли быть тогда отладка неоптимизированной компиляции, а?
Томаш Пшиходзки

int j = 0 for (; j <0x3e8; j ++) таким образом объявляется один раз раз как переменная, а не каждая за цикл. 2) Назначение это более толстый, чем все другие варианты. 3) Таким образом, правило bestpractice - это любое объявление вне итерации для.
Лука

1

Я тестировал на JS с Node 4.0.0, если кому-то интересно. Объявление вне цикла привело к повышению производительности примерно на 0,5 мс в среднем за 1000 испытаний со 100 миллионами итераций цикла на испытание. Так что я собираюсь сказать «вперед» и написать это наиболее читаемым / поддерживаемым способом, который B, IMO. Я бы поместил свой код в скрипку, но я использовал модуль Node для повышения производительности. Вот код:

var now = require("../node_modules/performance-now")

// declare vars inside loop
function varInside(){
    for(var i = 0; i < 100000000; i++){
        var temp = i;
        var temp2 = i + 1;
        var temp3 = i + 2;
    }
}

// declare vars outside loop
function varOutside(){
    var temp;
    var temp2;
    var temp3;
    for(var i = 0; i < 100000000; i++){
        temp = i
        temp2 = i + 1
        temp3 = i + 2
    }
}

// for computing average execution times
var insideAvg = 0;
var outsideAvg = 0;

// run varInside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varInside()
    var end = now()
    insideAvg = (insideAvg + (end-start)) / 2
}

// run varOutside a million times and average execution times
for(var i = 0; i < 1000; i++){
    var start = now()
    varOutside()
    var end = now()
    outsideAvg = (outsideAvg + (end-start)) / 2
}

console.log('declared inside loop', insideAvg)
console.log('declared outside loop', outsideAvg)

0

А) безопаснее, чем Б) ......... Представьте себе, если вы инициализируете структуру в цикле, а не в int или float, что тогда?

лайк

typedef struct loop_example{

JXTZ hi; // where JXTZ could be another type...say closed source lib 
         // you include in Makefile

}loop_example_struct;

//then....

int j = 0; // declare here or face c99 error if in loop - depends on compiler setting

for ( ;j++; )
{
   loop_example loop_object; // guess the result in memory heap?
}

Вы наверняка столкнетесь с проблемами утечки памяти! Поэтому я полагаю, что «А» - более безопасная ставка, в то время как «В» уязвим для накопления памяти, особенно при работе с библиотеками с закрытым исходным кодом.


0

Это интересный вопрос. Исходя из моего опыта, при обсуждении этого вопроса для кода возникает главный вопрос:

Есть ли причина, по которой переменная должна быть глобальной?

Имеет смысл объявить переменную только один раз, глобально, а не много раз локально, потому что это лучше для организации кода и требует меньше строк кода. Однако, если его нужно объявить только локально в одном методе, я бы инициализировал его в этом методе, чтобы было ясно, что переменная относится исключительно к этому методу. Будьте осторожны, чтобы не вызывать эту переменную вне метода, в котором она инициализируется, если вы выберете последний вариант - ваш код не будет знать, о чем вы говорите, и сообщит об ошибке.

Также, как примечание, не дублируйте имена локальных переменных между различными методами, даже если их цели почти идентичны; это просто сбивает с толку.


1
LOL Я не согласен по многим причинам ... Тем не менее, нет отрицательного голоса ... Я уважаю ваше право выбора
Grantly

0

это лучшая форма

double intermediateResult;
int i = byte.MinValue;

for(; i < 1000; i++)
{
intermediateResult = i;
System.out.println(intermediateResult);
}

1) таким способом объявляется один раз раз и переменная, и не каждая за цикл. 2) Назначение это более толстый, чем все другие варианты. 3) Таким образом, правило bestpractice - это любое объявление вне итерации для.


0

Попробовал то же самое в Go и сравнил вывод компилятора с go tool compile -Sпомощью go 1.9.4

Нулевая разница согласно выходу ассемблера.


0

У меня был тот же самый вопрос в течение длительного времени. Итак, я протестировал еще более простой кусок кода.

Вывод: Для таких случаев есть NO разница в производительности.

Случай внешней петли

int intermediateResult;
for(int i=0; i < 1000; i++){
    intermediateResult = i+2;
    System.out.println(intermediateResult);
}

Чехол внутри петли

for(int i=0; i < 1000; i++){
    int intermediateResult = i+2;
    System.out.println(intermediateResult);
}

Я проверил скомпилированный файл на декомпиляторе IntelliJ и в обоих случаях получил одно и то же Test.class

for(int i = 0; i < 1000; ++i) {
    int intermediateResult = i + 2;
    System.out.println(intermediateResult);
}

Я также разобрал код для обоих случаев, используя метод, приведенный в этом ответе . Я покажу только те части, которые имеют отношение к ответу

Случай внешней петли

Code:
  stack=2, locals=3, args_size=1
     0: iconst_0
     1: istore_2
     2: iload_2
     3: sipush        1000
     6: if_icmpge     26
     9: iload_2
    10: iconst_2
    11: iadd
    12: istore_1
    13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    16: iload_1
    17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
    20: iinc          2, 1
    23: goto          2
    26: return
LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13      13     1 intermediateResult   I
            2      24     2     i   I
            0      27     0  args   [Ljava/lang/String;

Чехол внутри петли

Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: iload_1
         3: sipush        1000
         6: if_icmpge     26
         9: iload_1
        10: iconst_2
        11: iadd
        12: istore_2
        13: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        16: iload_2
        17: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        20: iinc          1, 1
        23: goto          2
        26: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           13       7     2 intermediateResult   I
            2      24     1     i   I
            0      27     0  args   [Ljava/lang/String;

Если вы обратите внимание, только Slotназначено iи intermediateResultв LocalVariableTableобменена в качестве продукта порядка их появления. Такая же разница в слоте отражена в других строках кода.

  • Никаких дополнительных операций не выполняется
  • intermediateResult в обоих случаях это локальная переменная, поэтому нет разницы во времени доступа.

БОНУС

Компиляторы делают массу оптимизации, взгляните на то, что происходит в этом случае.

Нулевой случай

for(int i=0; i < 1000; i++){
    int intermediateResult = i;
    System.out.println(intermediateResult);
}

Нулевая работа декомпилирована

for(int i = 0; i < 1000; ++i) {
    System.out.println(i);
}

-1

Даже если я знаю, что мой компилятор достаточно умен, я не буду полагаться на него и буду использовать вариант а).

Вариант б) имеет смысл для меня, только если вам отчаянно нужно сделать промежуточный результат недоступным после тела цикла. Но я все равно не могу себе представить такую ​​отчаянную ситуацию ...

РЕДАКТИРОВАТЬ: Джон Скит сделал очень хорошую мысль, показав, что объявление переменной внутри цикла может иметь реальную семантическую разницу.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.