在JavaScript中遍历数组的最快方法是什么?


248

我从书中得知,您应该为循环编写这样的代码:

for(var i=0, len=arr.length; i < len; i++){
    // blah blah
}

因此arr.length不会每次都计算。

其他人则说编译器会对此做一些优化,因此您可以编写:

for(var i=0; i < arr.length; i++){
    // blah blah
}

我只想知道哪种是最佳实践?


1
处理数组循环时也值得一看:jsperf.com/array-loop-var-caching
cloakedninjas 2012年

@ wong2 t这基准Browserdiet具有替代的更完整的集合。
米(Domi)2014年


改进了先前的jsben:jsben.ch
Corbfon

我们可以介绍for ... of这场比赛的循环吗?语法似乎比没有缓存的for循环更容易,我想知道是否应该切换到for循环的使用。
programRaj

Answers:


339

在使用大多数现代浏览器执行此测试之后...

http://jsben.ch/dyM52

当前,最快的循环形式(我认为在语法上最明显)。

具有长度缓存的循环的标准

for (var i = 0, len = myArray.length; i < len; i++) {

}

我想肯定的是,我为JavaScript引擎开发人员鼓掌。应该优化运行时间以提高清晰度而不是聪明


6
有趣的是,在IE9中,速度更快:for(var i = 0,len = myArray.length; i <len; ++ i){} //前缀incr,而不是后缀
Christopher Bennage 2011年

4
有关其他使用原因,请参见使用前缀运算符优先后缀++i
Bennett McElwee

4
我按照@BennettMcElwee的建议使用前缀运算符进行了测试,它运行得更快一些: for(var i=0, len=myArray.length; i<len; ++i) 检查jsperf.com/caching-array-length/84
victmo 2012年

21
您必须小心使用此循环。我开始使用它,并且由于我犯了一个错误而很难跟踪错误。如果您嵌套两个这样的循环:jsfiddle.net/KQwmL/1。您必须小心在两个循环中为var len命名,否则第二个循环将覆盖第一个len。
Rui Marques 2012年

6
@WillshawMedia您可以使用单个var语句声明多个变量。如何编写,len实际上是您建议的范围。
jondavidjohn 2013年

90

遍历javascript数组的绝对最快的方法是:

var len = arr.length;
while (len--) {
    // blah blah
}

有关完整比较,请参见http://blogs.oracle.com/greimer/entry/best_way_to_code_a


1
不要忘记使用var(否则len将成为全局变量)。另外,请参阅jsperf.com/loops了解更多循环基准。
Mathias Bynens 2011年

22
这个答案所基于的博客文章现在已经有将近4年的历史了,那时js引擎发生了很多变化,请参阅下面的我的答案以获取更新的比较。
jondavidjohn

1
我同意@jondavidjohn。我测试了这段代码,结果发现效率更低。请检查jsperf.com/caching-array-length/84
victmo 2012年

上面的答案几乎普遍(跨浏览器)比for循环要慢得多。请参阅已接受答案中的JSPerf链接。真是太可惜了,因为它对IMO的可读性极高。
Letharion

3
我猜@jondavidjohn,“我在下面的答案”是什么意思,大声笑。
Shanimal

39

截至2016年6月,在最新的Chrome浏览器中进行了一些测试(2016年5月占浏览器市场的71%,并且还在不断增长):

  • 最快的循环是for循环,无论是否具有缓存长度都可以提供真正相似的性能。(具有缓存长度的for循环有时会比没有缓存的循环提供更好的结果,但是差异几乎可以忽略不计,这意味着引擎可能已经进行了优化以支持标准,并且可能是最直接的没有缓存的for循环)。
  • 带减量的while循环比for循环慢大约1.5倍。
  • 使用回调函数的循环(如标准的forEach)比for循环慢大约10倍。

我认为该线程太旧了,它会误导程序员以为他们需要缓存长度,或者使用递减的反向遍历来获得更好的性能,编写的代码比简单的for循环更易读,更容易出错。因此,我建议:

  • 如果您的应用程序迭代了很多项目,或者您的循环代码位于经常使用的函数中,那么简单的for循环就是答案:

    for (var i = 0; i < arr.length; i++) {
      // Do stuff with arr[i] or i
    }
  • 如果您的应用程序并没有真正遍历很多项目,或者您只需要在此处进行少量迭代,则使用标准的forEach回调或所选JS库中的任何类似函数可能会更易于理解且不易出错,因为索引变量作用域已关闭,您无需使用方括号即可直接访问数组值:

    arr.forEach(function(value, index) {
      // Do stuff with value or index
    });
  • 如果在迭代数十亿行时确实需要花费几毫秒的时间,而数组的长度在整个过程中都没有改变,则可以考虑将长度缓存在for循环中。尽管我认为现在确实没有必要:

    for (var i = 0, len = arr.length; i < len; i++) {
      // Do stuff with arr[i]
    }

不。jsbench.github.io/#67b13d4e78cdd0d7a7346410d5becf12显示最快的是“反向循环,隐式比较,内联代码”(105,221个操作/秒),而“循环,缓存的值,内联代码”仅获得76,635个操作/秒(Chrome 38.0.2125.111) )
Fr0sT

@ Fr0sT您的基准测试是另一种情况,将数组从索引1遍历到<=长度。当然,这将导致不同的结果。如果您尝试遍历<长度为零的基于零的数组-在我看来,这是通常的情况-您会发现,使用常规的“ for”循环(缓存的长度略快)可以更好地优化结果。
CGodo

Kyopaxa将基准更改为(0 <= i <长度),结果是相同的。“反向循环,隐式比较,函数调用”得分为365 kops / sec,而“循环,缓存值,内联代码”得分为350 kops / sec(FF 51)
Fr0sT

@ Fr0sT如果您更改没有相等比较的从零开始的缓存的for循环,例如for(let i=0, j=array.length; i < j; i++),forward循环将大大加快速度。在我进行的一些测试中,它赢了,在大多数情况下,它处于误差范围或反向循环之内。
艾萨克·B

1
@IsaacB和所有,很抱歉,我没有注意到工作台非常不正确-所有直接循环迭代1..length,而反向循环迭代length..0(arr [length]项无效)。我修复了测试,现在它们显示了以下结果:“循环,内联代码” 360,616 ops / sec±0.27%,“循环,内联代码的缓存值” 345,786 ops / sec±2.18%(Sic!)“反向循环,隐式比较,内联代码” 322,640 ops / sec±2.90%(!!!)。测试由FF51执行。新板凳在这里 jsbench.github.io/#6bdfcd2692ba80c16a68c88554281570。因此,丑化循环似乎没有意义。
Fr0sT

31

如果顺序不重要,则我更喜欢这种样式:

for(var i = array.length; i--; )

它缓存长度,并且要写的短得多。但是它将以相反的顺序遍历数组。


6
您刚刚杀死了它。
Vignesh Raja

您是否不需要i> = 0 ;?
MarwaAhmad

3
@MarwaAhmad:No. i--返回一个数字,一旦该数字0成为条件,则是false因为Boolean(0) === false
Felix Kling

29

只是2018年,所以更新可能会很好...

而且我真的不同意接受的答案。它在不同的浏览器上运行。有的做forEach快,有的for-loop,有的while 在这里是所有方法的基准http://jsben.ch/mW36e

arr.forEach( a => {
  // ...
}

由于您可以看到很多for循环,例如 for(a = 0; ... )因此值得一提的是,如果没有'var'变量,则会在全局范围内定义变量,这会极大地影响速度,因此速度会变慢。

Duff的设备在歌剧上运行得更快,但在Firefox中却没有

var arr = arr = new Array(11111111).fill(255);
var benches =     
[ [ "empty", () => {
  for(var a = 0, l = arr.length; a < l; a++);
}]
, ["for-loop", () => {
  for(var a = 0, l = arr.length; a < l; ++a)
    var b = arr[a] + 1;
}]
, ["for-loop++", () => {
  for(var a = 0, l = arr.length; a < l; a++)
    var b = arr[a] + 1;
}]
, ["for-loop - arr.length", () => {
  for(var a = 0; a < arr.length; ++a )
    var b = arr[a] + 1;
}]
, ["reverse for-loop", () => {
  for(var a = arr.length - 1; a >= 0; --a )
    var b = arr[a] + 1;
}]
,["while-loop", () => {
  var a = 0, l = arr.length;
  while( a < l ) {
    var b = arr[a] + 1;
    ++a;
  }
}]
, ["reverse-do-while-loop", () => {
  var a = arr.length - 1; // CAREFUL
  do {
    var b = arr[a] + 1;
  } while(a--);   
}]
, ["forEach", () => {
  arr.forEach( a => {
    var b = a + 1;
  });
}]
, ["for const..in (only 3.3%)", () => {
  var ar = arr.slice(0,arr.length/33);
  for( const a in ar ) {
    var b = a + 1;
  }
}]
, ["for let..in (only 3.3%)", () => {
  var ar = arr.slice(0,arr.length/33);
  for( let a in ar ) {
    var b = a + 1;
  }
}]
, ["for var..in (only 3.3%)", () => {
  var ar = arr.slice(0,arr.length/33);
  for( var a in ar ) {
    var b = a + 1;
  }
}]
, ["Duff's device", () => {
  var len = arr.length;
  var i, n = len % 8 - 1;

  if (n > 0) {
    do {
      var b = arr[len-n] + 1;
    } while (--n); // n must be greater than 0 here
  }
  n = (len * 0.125) ^ 0;
  if (n > 0) { 
    do {
      i = --n <<3;
      var b = arr[i] + 1;
      var c = arr[i+1] + 1;
      var d = arr[i+2] + 1;
      var e = arr[i+3] + 1;
      var f = arr[i+4] + 1;
      var g = arr[i+5] + 1;
      var h = arr[i+6] + 1;
      var k = arr[i+7] + 1;
    }
    while (n); // n must be greater than 0 here also
  }
}]];
function bench(title, f) {
  var t0 = performance.now();
  var res = f();
  return performance.now() - t0; // console.log(`${title} took ${t1-t0} msec`);
}
var globalVarTime = bench( "for-loop without 'var'", () => {
  // Here if you forget to put 'var' so variables'll be global
  for(a = 0, l = arr.length; a < l; ++a)
     var b = arr[a] + 1;
});
var times = benches.map( function(a) {
                      arr = new Array(11111111).fill(255);
                      return [a[0], bench(...a)]
                     }).sort( (a,b) => a[1]-b[1] );
var max = times[times.length-1][1];
times = times.map( a => {a[2] = (a[1]/max)*100; return a; } );
var template = (title, time, n) =>
  `<div>` +
    `<span>${title} &nbsp;</span>` +
    `<span style="width:${3+n/2}%">&nbsp;${Number(time.toFixed(3))}msec</span>` +
  `</div>`;

var strRes = times.map( t => template(...t) ).join("\n") + 
            `<br><br>for-loop without 'var' ${globalVarTime} msec.`;
var $container = document.getElementById("container");
$container.innerHTML = strRes;
body { color:#fff; background:#333; font-family:helvetica; }
body > div > div {  clear:both   }
body > div > div > span {
  float:left;
  width:43%;
  margin:3px 0;
  text-align:right;
}
body > div > div > span:nth-child(2) {
  text-align:left;
  background:darkorange;
  animation:showup .37s .111s;
  -webkit-animation:showup .37s .111s;
}
@keyframes showup { from { width:0; } }
@-webkit-keyframes showup { from { width:0; } }
<div id="container"> </div>


3
@Maykonn,您可能想说“它在Opera Mini以外的任何地方都可以使用”
dube

3
@Maykonn在默认视图中未列出,因为所有用户中有0.18%拥有IE8,您不应浪费时间尝试支持它。在2018年,这是一匹死马。
dube

1
如果考虑到世界各地的所有用户,则绝对是正确的。但是,不幸的是,IE8在世界上的某些地区还很重要。
Maykonn

1
如果可以的话,不仅不同的浏览器使用不同的方法将具有不同的结果,而且相同的浏览器使用不同的输入将具有不同的结果。巨大的仅数字数组将非常优化,而小的混合数组则不会。
Kaiido

1
@Tahlil谢谢。
nullqube

19

2014年 While又回来了

只是认为合乎逻辑。

看这个

for( var index = 0 , length = array.length ; index < length ; index++ ) {

 //do stuff

}
  1. 需要创建至少2个变量(索引,长度)
  2. 需要检查索引是否小于长度
  3. 需要增加索引
  4. 所述for环具有3个参数

现在告诉我为什么这应该快于:

var length = array.length;

while( --length ) { //or length--

 //do stuff

}
  1. 一个变量
  2. 没有检查
  3. 索引降低(机器更喜欢)
  4. while 只有一个参数

当Chrome 28显示for循环比while更快时,我感到非常困惑。这一定是本

“嗯,每个人都在使用for循环,让我们在开发chrome时重点关注它。”

但是现在,在2014年,while循环又回到了chrome上。它快了2倍,在其他/旧版浏览器中,它总是更快。

最近我做了一些新的测试。现在在现实世界中,这些短代码一文不值,而jsperf实际上无法正确执行while循环,因为它需要重新创建array.length,这也需要时间。

您无法获得jsperf上while循环的实际速度。

您需要创建自己的自定义函数并使用 window.performance.now()

是的... while循环根本不可能更快。

真正的问题实际上是dom操纵/渲染时间/绘制时间,或者您想称呼它。

例如,我有一个画布场景,我需要在其中计算坐标和碰撞...这是在10-200微秒(不是毫秒)之间完成的。渲染所有内容实际上需要花费几毫秒的时间。与DOM中的相同。

loop在某些情况下,还有另一种使用for的超级性能方式...例如,复制/克隆数组

for(
 var i = array.length ;
 i > 0 ;
 arrayCopy[ --i ] = array[ i ] // doing stuff
);

注意参数的设置:

  1. 与while循环相同,我仅使用一个变量
  2. 需要检查索引是否大于0;
  3. 如您所见,这种方法与每个人都使用的普通for循环不同,因为我在第3个参数内进行填充,并且我也直接在数组内进行递减。

话虽如此,这证实了像-

写道我正在考虑使它更短一些并删除一些无用的东西,并使用相同的样式来编写此代码:

for(
 var i = array.length ;
 i-- ;
 arrayCopy[ i ] = array[ i ] // doing stuff
);

即使时间更短,也似乎i多花一点时间会减慢一切。它比上一个for循环慢1/5 while

注:;是以后的looo非常重要的,而不{}

即使我只是告诉你jsperf并不是测试脚本的最佳方法..我在这里添加了这2个循环

http://jsperf.com/caching-array-length/40

这是关于javascript性能的另一个答案

https://stackoverflow.com/a/21353032/2450730

该答案是为了展示编写javascript的高效方式。因此,如果您看不懂,请提出疑问,您将获得答案或阅读有关javascript的书http://www.ecma-international.org/ecma-262/5.1/


这个答案开始很好。我注意到最近几年的for速度比快while,而我曾经阅读crome-dev的原因完全是因为您提到的原因。while再次赶上只是时间问题。从那时起,答案第一部分的逻辑将成立(再次,是的)!但是,现代实现不再严格遵循ecma指定的每个步骤(它们进行了优化)。因为现在您的引擎不再是最明显的瓶颈,所以现在可以实际上注意到反向循环中CPU缓存丢失
GitaarLAB

解释一下,所以也许我可以纠正答案或学习新知识。顺便说一句,答案现在已经有一年多的历史了……浏览器可能会像往常一样随着时间而改变……
cocco

在我看来,while(--length)是邪恶的,因为从技术上讲它是有效的,因为0为假,从语义上讲,0和false并不是真正的同一件事。
scott.korin 2015年

是的...这是一个较旧的职位...但是是的,我喜欢那段时间的简单性。可以肯定的是,在两种情况下,您都需要知道该写些什么。在另一边,我从来没有必要遍历谷底负数。
cocco 2015年

9
“机器更喜欢这个”听起来像是一则洗衣粉广告中的一句话
CocoaBean

11

http://jsperf.com/caching-array-length/60

我准备的最新版本的测试(通过重用较旧的版本)显示了一件事。

缓存长度不是很重要,但不会造成伤害。

在我的Debian Squeeze 64位(台式机硬件)中,上面链接的每个测试的第一次运行(在打开的新选项卡上)在Chrome,Opera和Firefox中的最后4个摘要(图表中的第3,第5,第7和第10个)上都给出了最佳结果)。随后的运行给出了完全不同的结果。

性能方面的结论很简单:

  • 使用for循环(正向)并使用!==代替进行测试<
  • 如果您以后不必重复使用该数组,则在递减的长度和破坏性的shift()-ing数组上进行while循环也是有效的。

tl; dr

如今(2011.10),以下模式似乎是最快的模式。

for (var i = 0, len = arr.length; i !== len; i++) {
    ...
}

请注意,缓存arr.length在这里并不是至关重要的,因此您可以进行测试i !== arr.length并且性能不会下降,但是您将获得较短的代码。


PS:我知道可以在代码段中使用shift()其结果而不是访问第0个元素,但是在重用以前的修订版本(while循环有误)之后,我以某种方式忽略了它,后来又不想失去已经获得的结果。


像let current = arr [i]这样在循环内创建变量会降低性能(大内存分配)吗?还是在循环之前声明当前值会更好?还是在循环内的所有位置使用arr [i]?
马卡洛夫·谢尔盖

8

“最好”的纯表演?或性能AND可读性?

纯粹的性能“最好”是这样,它使用缓存和++ prefix运算符(我的数据:http : //jsperf.com/caching-array-length/189

for (var i = 0, len = myArray.length; i < len; ++i) {
  // blah blah
}

我认为无缓存的for循环是执行时间和程序员读取时间的最佳平衡。每一位以C / C ++ / Java开头的程序员都不会浪费ms来阅读这本书。

for(var i=0; i < arr.length; i++){
  // blah blah
}

2
+1以提高可读性。无论len命名得多么好,一个人总是必须在第一个循环上做两次尝试。第二个循环的意图很明显。
乔什·约翰逊

7

**将数组长度缓存到循环中,将花费一些时间。如果数组中有更多项,则取决于数组中的项,相对于时间Ms,存在主要差异*

**

sArr; //Array[158];

for(var i = 0 ; i <sArr.length ; i++) {
 callArray(sArr[i]); //function call
}

***end: 6.875ms***

**

**

sArr; //Array[158];
for(var i = 0,len = sArr.length ; i < len ; i++) {
  callArray(sArr[i]); //function call
}

***end: 1.354ms***

**


6

似乎是迄今为止最快的方法

var el;
while (el = arr.shift()) {
  el *= 2;
}

考虑到这会消耗数组,吃掉它,什么也没留下……


2
arr.shift();而不是arr.pop() 这样,可以避免数组反向。
Tintu C Raju 2015年

1
@Gargaroz(如果从Web服务获取JSON,例如聊天服务或产品目录中的项目)。当您只需要使用一次数组时的另一种情况是,例如,一张图表,它间隔获取许多坐标。有很多例子。
塞尔吉奥

太好了,谢谢您的解释,非常感谢您。您能指出我可以找到更多利用这种循环的例子的方向吗?
Gargaroz '16

1
目前在Chrome 53和Firefox 48中,这是最慢的方法之一-请查看perfjs.info/array-iteration
Pencroff

1
@Alireza同意,我也在回答中对此发表评论。
塞尔吉奥

4

今年是2017年

我做了一些测试。

https://jsperf.com/fastest-way-to-iterate-through-an-array/

看来该while方法在Chrome上是最快的。

看起来像左递减(--i)是比别人快很多(++ii--i++在Firefox)。

这种方法平均而言是禁食的。但是它以相反的顺序迭代数组。

let i = array.length;
while (--i >= 0) {
    doSomething(array[i]);
}

如果向前的顺序很重要,请使用此方法。

let ii = array.length;
let i = 0;
while (i < ii) {
    doSomething(array[i]);
    ++i;
}

3
通过使用关键字,let您实际上是在比较作用域创建性能而不是循环性能。let i = 0, ii = array.lengthfor循环中使用将为for块内的这些变量创建新的作用域。您的while示例没有为while块内的变量创建新的作用域,这就是它们更快的原因。如果您在for循环中使用var而不是let,您将看到for循环如何仍然与2017年一样快,但可读性更高。
CGodo

这是我正在谈论的jsperf
CGodo

这只是Chrome中的问题。在其他浏览器var,并let具有相同的性能- stackoverflow.com/a/32345435/1785975
SeregPie

有趣。无论如何,我发现“ while在Chrome中更快”的说法并不准确。仅let由于Chrome中该关键字的性能问题而使用。如果使用var或与其他浏览器一起使用,for并且while几乎相同,则有时for视基准测试而定甚至更快,并且它更加紧凑和可读。
CGodo

2

我总是写第一种风格。

即使编译器足够聪明,可以针对数组优化它,但是如果我们在这里使用DOMNodeList或一些具有计算长度的复杂对象,它仍然很聪明?

我知道关于数组的问题,但是我认为以一种样式编写所有循环是一个好习惯。


1
var arr = []; // The array
var i = 0;
while (i < arr.length) {
    // Do something with arr[i]
    i++;
}

i ++比++ i,-i和i--快

另外,您可以保存最后一次需要访问i时执行arr [i ++]的最后一行(但这可能很难调试)。

您可以在这里进行测试(以及其他循环测试):http : //jsperf.com/for-vs-whilepop/5


1
当前在Chrome 53中是正确的,但是Firefox 48具有相同的速度-检查perfjs.info/array-iteration
Pencroff


0

我尝试了其他方法来迭代一个巨大的数组,发现将数组长度减半,然后在一个循环中将两个半部迭代都更快。在处理巨大数组时可以看到这种性能差异。

var firstHalfLen =0;
var secondHalfLen = 0;
var count2=0;
var searchterm = "face";
var halfLen = arrayLength/2;
if(arrayLength%2==halfLen)
{
   firstHalfLen = Math.ceil(halfLen);
   secondHalfLen=Math.floor(halfLen);
}
else
{
   firstHalfLen=halfLen;
   secondHalfLen=halfLen;
}
for(var firstHalfCOunter=0,secondHalfCounter = arrayLength-secondHalfLen;
    firstHalfCOunter < firstHalfLen;
    firstHalfCOunter++)
{
  if(mainArray[firstHalfCOunter].search(new RegExp(searchterm, "i"))> -1)
  {
    count2+=1;
  }
  if(secondHalfCounter < arrayLength)
  {
    if(mainArray[secondHalfCounter].search(new RegExp(searchterm, "i"))> -1)
    {
        count2+=1;
    }
    secondHalfCounter++; 
  }
}

以上方法对缓存长度的for VS循环之间的性能进行了一些比较(使用timer.js)。

http://jsfiddle.net/tejzpr/bbLgzxgo/


0

另一个jsperf.com测试:http://jsperf.com/while-reverse-vs-for-cached-length

相反的while循环似乎是最快的。唯一的问题是,虽然(--i)将停止在0。然后如何在循环中访问array [0]?


2
如果您这样做while (i--)i则将在递减而不是递减然后测试真实性之前先测试的真实性。
贾斯汀·费舍尔

0

截至2017年9月,这些jsperf测试显示以下模式在Chrome 60上性能最高:

function foo(x) {
 x;
};
arr.forEach(foo);

有人能够繁殖吗?


是的,它似乎最快,但是尝试在IE11中运行它,这些选项是最慢的。在Firefox 55.03中,“旧的缓存缓存镜头”达到1200万,与3.3k的chrome相比,其性能惊人。为了使所有浏览器的性能保持一致,您应该为每个浏览器使用最快的平均循环。
Plippie


0

While循环比for循环快一点。

var len = arr.length;
while (len--) {
    // blah blah
}

改用while循环



-1

我所知道的最优雅的解决方案是使用地图。

var arr = [1,2,3];
arr.map(function(input){console.log(input);});

46
问题并没有要求通过循环进行迭代的最慢方法
真棒的2013年

-1

试试这个:

var myarray =[],
i = myarray.lenght;
while(i--){
// do somthing
}

-1

在数组中循环的更快方法是使用过滤器。filter()方法创建一个新数组,其中所有元素都通过了由提供的函数实现的测试。

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

const words = ['Floccinaucinihilipilification', 'limit', 'elite', 'Hippopotomonstrosesquipedaliophobia', 'destruction', 'present'];

const result = words.filter(word => word.length > 6);

console.log(new Date(), result);

根据我的经验,我一直喜欢过滤器,地图等。


问题是要在最短的时间内遍历数组,而不是将数组复制到新数组中。
Rahul Kadukar '19

-1

截至2019年,WebWorker变得越来越流行,对于大型数据集,我们可以使用WebWorker通过充分利用多核处理器来更快地处理数据。

我们还拥有Parallel.js,这使WebWorker更加易于用于数据处理。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.