【发布时间】:2022-01-20 23:08:51
【问题描述】:
我正在尝试根据第一原理编写 JavaScript 分形生成算法。我知道那里有很多示例,但我想结合额外的功能来支持 Mandelbrot 和“旋转”Julia,以及“Burning Ship”和“Tricorn”等变体。考虑到这一点,我实现了一个轻量级的 Complex 数学库(同样,我知道那里有标准的 Complex js 库,但我想从头开始构建一个作为学习练习)。
我测试了两个替代函数,一个使用标准数学函数fractal,另一个使用我的复杂库方法fractalComplex。它们都运行良好,但我惊讶地发现标准版本几乎是复杂版本的 两倍。我期待一些额外的开销,但没有那么多!
谁能解释为什么? Complex 库“在幕后”使用相同的数学结构。额外的开销是否完全取决于对象创建?
代码复制如下(输入参数 z 和 c 是 {re, im} 形式的对象)。
function fractal(z, c, maxiter) {
var i, za, re, im, re2, im2;
c = (settype === JULIA ? c : z);
// Iterate until abs(z) exceeds escape radius
for (i = 0; i < maxiter; i += 1) {
if (setvar === BURNING_SHIP) {
re = Math.abs(z.re);
im = -Math.abs(z.im);
}
else if (setvar === TRICORN) {
re = z.re
im = -z.im; // conjugate z
}
else { // Mandelbrot
re = z.re;
im = z.im;
}
re2 = re * re;
im2 = im * im;
z = { // z = z² + c
re: re2 - im2 + c.re,
im: 2 * im * re + c.im
};
za = re2 + im2 // abs(z)²
if (za > 4) { // abs(z)² > radius²
break;
}
}
za = Math.sqrt(za); // abs(z)
return { i, za };
}
function fractalComplex(z, c, maxiter, n, radius) {
var i, za;
c = (settype === JULIA ? c : z);
// Iterate until abs(z) exceeds escape radius
for (i = 0; i < maxiter; i += 1) {
if (setvar === BURNING_SHIP) {
z = new Complex(Math.abs(z.re), -Math.abs(z.im))
}
if (setvar === TRICORN) {
z = z.conjugate()
}
z = z.quad(n, c); // z = zⁿ + c
za = z.abs();
if (za > radius) {
break;
}
}
return { i, za };
}
我的“复杂精简版”库如下:
// ------------------------------------------------------------------------
// A basic complex number library which implements the methods used for
// Mandelbrot and Julia Set generation.
// ------------------------------------------------------------------------
'use strict';
// Instantiate complex number object.
function Complex(re, im) {
this.re = re; // real
this.im = im; // imaginary
}
Complex.prototype = {
're': 0,
'im': 0,
// Set value.
'set': function (re, im) {
this.re = re;
this.im = im;
},
// Get magnitude.
'abs': function () {
return Math.sqrt(this.re * this.re + this.im * this.im);
},
// Get polar representation (r, θ); angle in radians.
'polar': function () {
return { r: this.abs(), θ: Math.atan2(this.im, this.re) };
},
// Get square.
'sqr': function () {
var re2 = this.re * this.re - this.im * this.im;
var im2 = 2 * this.im * this.re;
return new Complex(re2, im2);
},
// Get complex number to the real power n.
'pow': function (n) {
if (n === 0) { return new Complex(1, 0); }
if (n === 1) { return this; }
if (n === 2) { return this.sqr(); }
var pol = this.polar();
var rn = Math.pow(pol.r, n);
var θn = n * pol.θ;
return cart(rn, θn);
},
// Get conjugate.
'conjugate': function () {
return new Complex(this.re, -this.im);
},
// Get quadratic zⁿ + c.
'quad': function (n, c) {
var zn = this.pow(n);
return new Complex(zn.re + c.re, zn.im + c.im);
},
// Rotate by angle in radians.
'rotate': function (angle) {
var pol = this.polar();
angle += pol.θ;
return new Complex(pol.r * Math.cos(angle), pol.r * Math.sin(angle));
},
// String in exponent format to specified significant figures.
'toString': function (sig = 9) {
return this.re.toExponential(sig) + " + " + this.im.toExponential(sig) + "i";
},
}
// Convert polar (r, θ) to cartesian representation (re, im).
function cart(r, θ) {
var re = r * Math.cos(θ);
var im = r * Math.sin(θ);
return new Complex(re, im);
}
补充编辑 22/12/2021 11:52:
对于它的价值,这就是我最终决定的......
function fractal(p, c, n, maxiter, radius) {
var i, za, zre, zim, tre, cre, cim, r, θ;
var lastre = 0;
var lastim = 0;
var per = 0;
if (setmode === JULIA) {
cre = c.re;
cim = c.im;
zre = p.re;
zim = p.im;
}
else { // Mandelbrot mode
cre = p.re;
cim = p.im;
zre = 0;
zim = 0;
}
// Iterate until abs(z) exceeds escape radius
for (i = 0; i < maxiter; i += 1) {
if (setvar === BURNING_SHIP) {
zre = Math.abs(zre);
zim = -Math.abs(zim);
}
else if (setvar === TRICORN) {
zim = -zim; // conjugate z
}
// z = z² + c
if (n == 2) {
tre = zre * zre - zim * zim + cre;
zim = 2 * zre * zim + cim;
zre = tre;
}
else { // z = zⁿ + c, where n is integer > 2
r = powi(Math.sqrt(zre * zre + zim * zim), n); // radiusⁿ
//r = Math.pow(Math.sqrt(zre * zre + zim * zim), n); // radiusⁿ
θ = n * Math.atan2(zim, zre); // angleⁿ
zre = r * Math.cos(θ) + cre;
zim = r * Math.sin(θ) + cim;
}
// Optimisation - periodicity check speeds
// up processing of points within set
if (PERIODCHECK) {
if (zre === lastre && zim === lastim) {
i = maxiter;
break;
}
per += 1;
if (per > 20) {
per = 0;
lastre = zre;
lastim = zim;
}
}
// ... end of optimisation
za = zre * zre + zim * zim // abs(z)²
if (za > radius) { // abs(z)² > radius²
break;
}
}
return { i, za };
}
// Optimised pow() function for integer exponents
// using 'halving and squaring'.
function powi(base, n) {
var res = 1;
while (n) {
if (n & 1) { // if n is odd
res *= base;
}
n >>= 1; // n * 2
base *= base;
}
return res;
}
【问题讨论】:
-
你用什么引擎来运行代码,你尝试了多少次迭代?
-
@Bergi 我已经在 Firefox 浏览器 (95.0.1) 和 Node.js 16.13.1 下本地运行它(目的是部署为网站或电子应用程序) .对于 maxiter,我使用了 100 的固定 maxiter 和自动增加缩放级别的 maxiter 的算法(见下文)。我在所有情况下都看到相同的性能差异:
function getAutoiter(zoom) { return Math.max(MAXITER, parseInt(Math.abs(1000 * Math.log(1 / Math.sqrt(zoom))))); } -
@Bergi 给你一些指标;基于 750 x 500 像素的画布,我看到
fractal版本的总执行时间约为 190 毫秒,fractalComplex版本的总执行时间为 280 毫秒(其余代码相同)。 -
"Complex 库“在幕后”使用相同的数学结构。" 请澄清此语句的含义,因为
fractal和fractalComplex不仅具有不同的参数,而且后者中的附加radius参数参与打破for循环的逻辑...加上z = z**2 + c的fractal计算在fractalComplex中被替换为对quad的调用,这然后调用pow,后者又调用polar和cart...即,在fractal计算中似乎没有类似的极坐标和笛卡尔坐标转换... -
pow,真正的变体,在大多数数学库中是一个相当复杂的过程。exp(y*ln(x))不够准确。使用减半和平方的整数幂低于 5,甚至可能低于 10,速度更快。
标签: javascript performance fractals