Improving the speed of creation for three Perlin Noise Maps in Python?












4












$begingroup$


I am interested in learning how I can improve the speed of the code in this pygame file. I iterate over 6400 * 1800 * 3 or 34,560,000 elements of various numpy arrays here to apply noise values to them. The noise library I'm using can be found on GitHub here.



I am calling static variables from a class called ST here. ST.MAP_WIDTH = 6400 and ST.MAP_HEIGHT = 1800. All other ST attributes called here are assigned in the code. They are the noise-maps I'm making.



from __future__ import division
from singleton import ST
import numpy as np
import noise
import timeit
import random
import math


def __noise(noise_x, noise_y, octaves=1, persistence=0.5, lacunarity=2.0):
"""
Generates and returns a noise value.

:param noise_x: The noise value of x
:param noise_y: The noise value of y
:return: numpy.float32
"""

value = noise.pnoise2(noise_x, noise_y,
octaves, persistence, lacunarity,
random.randint(1, 9999))

return np.float32(value)


def __elevation_mapper(noise_x, noise_y):
"""
Finds and returns the elevation noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return __noise(noise_x, noise_y, 8, 0.9)


def __climate_mapper(y, noise_x, noise_y):
"""
Finds and returns the climate noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
# find distance from bottom of map and normalize to range [0, 1]
distance = math.sqrt((y - (ST.MAP_HEIGHT >> 1))**2) / ST.MAP_HEIGHT

value = __noise(noise_x, noise_y, 8, 0.7)

return (1 + value - distance) / 2


def __rainfall_mapper(noise_x, noise_y):
"""
Finds and returns the rainfall noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return __noise(noise_x, noise_y, 4, 0.65, 2.5)


def create_map_arr():
"""
This function creates the elevation, climate, and rainfall noise maps,
normalizes them to the range [0, 1], and then assigns them to their
appropriate attributes in the singleton ST.
"""

start = timeit.default_timer()

elevation_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
climate_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
rainfall_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)

randomizer = random.uniform(0.0001, 0.9999)

# assign noise map values
for y in range(ST.MAP_HEIGHT):
for x in range(ST.MAP_WIDTH):
noise_x = x / ST.MAP_WIDTH - randomizer
noise_y = y / ST.MAP_HEIGHT - randomizer

elevation_arr[y][x] = __elevation_mapper(noise_x, noise_y)
climate_arr[y][x] = __climate_mapper(y, noise_x, noise_y)
rainfall_arr[y][x] = __rainfall_mapper(noise_x, noise_y)

# normalize to range [0, 1] and assign to relevant ST attributes
ST.ELEVATIONS = (elevation_arr - elevation_arr.min()) /
(elevation_arr.max() - elevation_arr.min())

ST.CLIMATES = (climate_arr - climate_arr.min()) /
(climate_arr.max() - climate_arr.min())

ST.RAINFALLS = (rainfall_arr - rainfall_arr.min()) /
(rainfall_arr.max() - rainfall_arr.min())

stop = timeit.default_timer()
print("GENERATION TIME: " + str(stop - start))









share|improve this question











$endgroup$








  • 1




    $begingroup$
    Have a look at numpy.meshgrid and its examples. I think they'll do what you need.
    $endgroup$
    – Austin Hastings
    6 hours ago
















4












$begingroup$


I am interested in learning how I can improve the speed of the code in this pygame file. I iterate over 6400 * 1800 * 3 or 34,560,000 elements of various numpy arrays here to apply noise values to them. The noise library I'm using can be found on GitHub here.



I am calling static variables from a class called ST here. ST.MAP_WIDTH = 6400 and ST.MAP_HEIGHT = 1800. All other ST attributes called here are assigned in the code. They are the noise-maps I'm making.



from __future__ import division
from singleton import ST
import numpy as np
import noise
import timeit
import random
import math


def __noise(noise_x, noise_y, octaves=1, persistence=0.5, lacunarity=2.0):
"""
Generates and returns a noise value.

:param noise_x: The noise value of x
:param noise_y: The noise value of y
:return: numpy.float32
"""

value = noise.pnoise2(noise_x, noise_y,
octaves, persistence, lacunarity,
random.randint(1, 9999))

return np.float32(value)


def __elevation_mapper(noise_x, noise_y):
"""
Finds and returns the elevation noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return __noise(noise_x, noise_y, 8, 0.9)


def __climate_mapper(y, noise_x, noise_y):
"""
Finds and returns the climate noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
# find distance from bottom of map and normalize to range [0, 1]
distance = math.sqrt((y - (ST.MAP_HEIGHT >> 1))**2) / ST.MAP_HEIGHT

value = __noise(noise_x, noise_y, 8, 0.7)

return (1 + value - distance) / 2


def __rainfall_mapper(noise_x, noise_y):
"""
Finds and returns the rainfall noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return __noise(noise_x, noise_y, 4, 0.65, 2.5)


def create_map_arr():
"""
This function creates the elevation, climate, and rainfall noise maps,
normalizes them to the range [0, 1], and then assigns them to their
appropriate attributes in the singleton ST.
"""

start = timeit.default_timer()

elevation_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
climate_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
rainfall_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)

randomizer = random.uniform(0.0001, 0.9999)

# assign noise map values
for y in range(ST.MAP_HEIGHT):
for x in range(ST.MAP_WIDTH):
noise_x = x / ST.MAP_WIDTH - randomizer
noise_y = y / ST.MAP_HEIGHT - randomizer

elevation_arr[y][x] = __elevation_mapper(noise_x, noise_y)
climate_arr[y][x] = __climate_mapper(y, noise_x, noise_y)
rainfall_arr[y][x] = __rainfall_mapper(noise_x, noise_y)

# normalize to range [0, 1] and assign to relevant ST attributes
ST.ELEVATIONS = (elevation_arr - elevation_arr.min()) /
(elevation_arr.max() - elevation_arr.min())

ST.CLIMATES = (climate_arr - climate_arr.min()) /
(climate_arr.max() - climate_arr.min())

ST.RAINFALLS = (rainfall_arr - rainfall_arr.min()) /
(rainfall_arr.max() - rainfall_arr.min())

stop = timeit.default_timer()
print("GENERATION TIME: " + str(stop - start))









share|improve this question











$endgroup$








  • 1




    $begingroup$
    Have a look at numpy.meshgrid and its examples. I think they'll do what you need.
    $endgroup$
    – Austin Hastings
    6 hours ago














4












4








4


1



$begingroup$


I am interested in learning how I can improve the speed of the code in this pygame file. I iterate over 6400 * 1800 * 3 or 34,560,000 elements of various numpy arrays here to apply noise values to them. The noise library I'm using can be found on GitHub here.



I am calling static variables from a class called ST here. ST.MAP_WIDTH = 6400 and ST.MAP_HEIGHT = 1800. All other ST attributes called here are assigned in the code. They are the noise-maps I'm making.



from __future__ import division
from singleton import ST
import numpy as np
import noise
import timeit
import random
import math


def __noise(noise_x, noise_y, octaves=1, persistence=0.5, lacunarity=2.0):
"""
Generates and returns a noise value.

:param noise_x: The noise value of x
:param noise_y: The noise value of y
:return: numpy.float32
"""

value = noise.pnoise2(noise_x, noise_y,
octaves, persistence, lacunarity,
random.randint(1, 9999))

return np.float32(value)


def __elevation_mapper(noise_x, noise_y):
"""
Finds and returns the elevation noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return __noise(noise_x, noise_y, 8, 0.9)


def __climate_mapper(y, noise_x, noise_y):
"""
Finds and returns the climate noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
# find distance from bottom of map and normalize to range [0, 1]
distance = math.sqrt((y - (ST.MAP_HEIGHT >> 1))**2) / ST.MAP_HEIGHT

value = __noise(noise_x, noise_y, 8, 0.7)

return (1 + value - distance) / 2


def __rainfall_mapper(noise_x, noise_y):
"""
Finds and returns the rainfall noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return __noise(noise_x, noise_y, 4, 0.65, 2.5)


def create_map_arr():
"""
This function creates the elevation, climate, and rainfall noise maps,
normalizes them to the range [0, 1], and then assigns them to their
appropriate attributes in the singleton ST.
"""

start = timeit.default_timer()

elevation_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
climate_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
rainfall_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)

randomizer = random.uniform(0.0001, 0.9999)

# assign noise map values
for y in range(ST.MAP_HEIGHT):
for x in range(ST.MAP_WIDTH):
noise_x = x / ST.MAP_WIDTH - randomizer
noise_y = y / ST.MAP_HEIGHT - randomizer

elevation_arr[y][x] = __elevation_mapper(noise_x, noise_y)
climate_arr[y][x] = __climate_mapper(y, noise_x, noise_y)
rainfall_arr[y][x] = __rainfall_mapper(noise_x, noise_y)

# normalize to range [0, 1] and assign to relevant ST attributes
ST.ELEVATIONS = (elevation_arr - elevation_arr.min()) /
(elevation_arr.max() - elevation_arr.min())

ST.CLIMATES = (climate_arr - climate_arr.min()) /
(climate_arr.max() - climate_arr.min())

ST.RAINFALLS = (rainfall_arr - rainfall_arr.min()) /
(rainfall_arr.max() - rainfall_arr.min())

stop = timeit.default_timer()
print("GENERATION TIME: " + str(stop - start))









share|improve this question











$endgroup$




I am interested in learning how I can improve the speed of the code in this pygame file. I iterate over 6400 * 1800 * 3 or 34,560,000 elements of various numpy arrays here to apply noise values to them. The noise library I'm using can be found on GitHub here.



I am calling static variables from a class called ST here. ST.MAP_WIDTH = 6400 and ST.MAP_HEIGHT = 1800. All other ST attributes called here are assigned in the code. They are the noise-maps I'm making.



from __future__ import division
from singleton import ST
import numpy as np
import noise
import timeit
import random
import math


def __noise(noise_x, noise_y, octaves=1, persistence=0.5, lacunarity=2.0):
"""
Generates and returns a noise value.

:param noise_x: The noise value of x
:param noise_y: The noise value of y
:return: numpy.float32
"""

value = noise.pnoise2(noise_x, noise_y,
octaves, persistence, lacunarity,
random.randint(1, 9999))

return np.float32(value)


def __elevation_mapper(noise_x, noise_y):
"""
Finds and returns the elevation noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return __noise(noise_x, noise_y, 8, 0.9)


def __climate_mapper(y, noise_x, noise_y):
"""
Finds and returns the climate noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
# find distance from bottom of map and normalize to range [0, 1]
distance = math.sqrt((y - (ST.MAP_HEIGHT >> 1))**2) / ST.MAP_HEIGHT

value = __noise(noise_x, noise_y, 8, 0.7)

return (1 + value - distance) / 2


def __rainfall_mapper(noise_x, noise_y):
"""
Finds and returns the rainfall noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return __noise(noise_x, noise_y, 4, 0.65, 2.5)


def create_map_arr():
"""
This function creates the elevation, climate, and rainfall noise maps,
normalizes them to the range [0, 1], and then assigns them to their
appropriate attributes in the singleton ST.
"""

start = timeit.default_timer()

elevation_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
climate_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
rainfall_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)

randomizer = random.uniform(0.0001, 0.9999)

# assign noise map values
for y in range(ST.MAP_HEIGHT):
for x in range(ST.MAP_WIDTH):
noise_x = x / ST.MAP_WIDTH - randomizer
noise_y = y / ST.MAP_HEIGHT - randomizer

elevation_arr[y][x] = __elevation_mapper(noise_x, noise_y)
climate_arr[y][x] = __climate_mapper(y, noise_x, noise_y)
rainfall_arr[y][x] = __rainfall_mapper(noise_x, noise_y)

# normalize to range [0, 1] and assign to relevant ST attributes
ST.ELEVATIONS = (elevation_arr - elevation_arr.min()) /
(elevation_arr.max() - elevation_arr.min())

ST.CLIMATES = (climate_arr - climate_arr.min()) /
(climate_arr.max() - climate_arr.min())

ST.RAINFALLS = (rainfall_arr - rainfall_arr.min()) /
(rainfall_arr.max() - rainfall_arr.min())

stop = timeit.default_timer()
print("GENERATION TIME: " + str(stop - start))






python performance python-2.x numpy






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited 7 hours ago







LuminousNutria

















asked 9 hours ago









LuminousNutriaLuminousNutria

1805




1805








  • 1




    $begingroup$
    Have a look at numpy.meshgrid and its examples. I think they'll do what you need.
    $endgroup$
    – Austin Hastings
    6 hours ago














  • 1




    $begingroup$
    Have a look at numpy.meshgrid and its examples. I think they'll do what you need.
    $endgroup$
    – Austin Hastings
    6 hours ago








1




1




$begingroup$
Have a look at numpy.meshgrid and its examples. I think they'll do what you need.
$endgroup$
– Austin Hastings
6 hours ago




$begingroup$
Have a look at numpy.meshgrid and its examples. I think they'll do what you need.
$endgroup$
– Austin Hastings
6 hours ago










1 Answer
1






active

oldest

votes


















2












$begingroup$

Losing your Loops



Austin Hastings' comment gives you a good hint where to look at. The main takeaway for you should be:



(Most) loops are damn slow in Python. Especially multiple nested loops.



NumPy can help to vectorize your code, i.e. in this case that more of the looping is done in the C backend instead of in the Python interpreter. I would highly recommend to have a listen to the talk Losing your Loops: Fast Numerical Computing with NumPy by Jake VanderPlas. Although primarily tailored towards data science, it gives a good overview on the topic.



I did some slight modifications to your original script to include some of the vectorization ideas while still using your chosen Perlin noise library. (Sidenote: I changed the __ prefix to a single _, because that is the convention most Python programmers use for internal functions. See PEP8 style guide.)



# -*- coding: utf-8 -*-
from __future__ import division, print_function
import numpy as np
import noise
import timeit


class ST(object):
MAP_HEIGHT = 1800
MAP_WIDTH = 6400


def _noise(noise_x, noise_y, octaves=1, persistence=0.5, lacunarity=2.0):
"""
Generates and returns a noise value.

:param noise_x: The noise value of x
:param noise_y: The noise value of y
:return: numpy.float32
"""
if isinstance(noise_x, np.ndarray):
#rand_seed = np.random.randint(1, 9999, noise_x.size)
rand_seed = np.ones((noise_x.size, )) # just for comparison
value = np.array([noise.pnoise2(x, y, octaves, persistence, lacunarity, r)
for x, y, r in zip(noise_x.flat, noise_y.flat, rand_seed)])
return value.reshape(noise_x.shape)
else:
value = noise.pnoise2(noise_x, noise_y,
octaves, persistence, lacunarity,
1.0) # just for comparison
#np.random.randint(1, 9999))
return np.float32(value)


def _elevation_mapper(noise_x, noise_y):
"""
Finds and returns the elevation noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return _noise(noise_x, noise_y, 8, 0.9)


def _climate_mapper(y, noise_x, noise_y):
"""
Finds and returns the climate noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
# find distance from bottom of map and normalize to range [0, 1]
distance = np.sqrt((y - (ST.MAP_HEIGHT >> 1))**2) / ST.MAP_HEIGHT

value = _noise(noise_x, noise_y, 8, 0.7)

return (1.0 + value - distance) / 2.0


def _rainfall_mapper(noise_x, noise_y):
"""
Finds and returns the rainfall noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return _noise(noise_x, noise_y, 4, 0.65, 2.5)


def create_map_arr():
"""
This function creates the elevation, climate, and rainfall noise maps,
normalizes them to the range [0, 1], and then assigns them to their
appropriate attributes in the singleton ST.
"""
# assign noise map values
randomizer = np.random.uniform(0.0001, 0.9999)

start_arr = timeit.default_timer()

X, Y = np.mgrid[0:ST.MAP_WIDTH, 0:ST.MAP_HEIGHT]
noise_x = X / ST.MAP_WIDTH - randomizer
noise_y = Y / ST.MAP_HEIGHT - randomizer
elevation_arr_np = _elevation_mapper(noise_x, noise_y)
climate_arr_np = _climate_mapper(Y, noise_x, noise_y)
rainfall_arr_np = _rainfall_mapper(noise_x, noise_y)

duration_arr = timeit.default_timer() - start_arr

start_loop = timeit.default_timer()

elevation_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
climate_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
rainfall_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
for y in range(ST.MAP_HEIGHT):
for x in range(ST.MAP_WIDTH):
noise_x = x / ST.MAP_WIDTH - randomizer
noise_y = y / ST.MAP_HEIGHT - randomizer

elevation_arr[y, x] = _elevation_mapper(noise_x, noise_y)
climate_arr[y, x] = _climate_mapper(y, noise_x, noise_y)
rainfall_arr[y, x] = _rainfall_mapper(noise_x, noise_y)

duration_loop = timeit.default_timer() - start_loop

print(np.allclose(elevation_arr, elevation_arr_np.T))
print(np.allclose(climate_arr, climate_arr_np.T))
print(np.allclose(rainfall_arr, rainfall_arr_np.T))

print("GENERATION TIME: loop: {:.6f}, array: {:.6f}".format(duration_loop, duration_arr))

if __name__ == "__main__":
create_map_arr()


The bottleneck is still in



value = np.array([noise.pnoise2(x, y, octaves, persistence, lacunarity, r)
for x, y, r in zip(noise_x.flat, noise_y.flat, rand_seed)])


and it would be highly favorable to use an implementation which supports 2D input, preferably from NumPy, directly (see further reading below).



Nevertheless, the modifications bring the execution time down to a third of the original time on my machine (which is not that powerful):



True
True
True
GENERATION TIME: loop: 338.094228, array: 101.549388


Those three Trues are from a little test I added to check if the generated maps are the same within reasonable accuracy. For this purpose the additional random value in _noise was disabled.



Further reading



There have also already been similar questions on Code Review (see, e.g. here), where a reviewer created a Perlin noise implementation purely in Numpy. There also seems to be a GitHub project also doing Perlin noise with Numpy. So maybe have a look at them if your not forced to stick with noise.






share|improve this answer









$endgroup$













  • $begingroup$
    Thank you! I will take a look at the other ways to generate Perlin Noise. I was only using that library because it was the fastest I'd found so far.
    $endgroup$
    – LuminousNutria
    4 hours ago











Your Answer





StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");

StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f216186%2fimproving-the-speed-of-creation-for-three-perlin-noise-maps-in-python%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









2












$begingroup$

Losing your Loops



Austin Hastings' comment gives you a good hint where to look at. The main takeaway for you should be:



(Most) loops are damn slow in Python. Especially multiple nested loops.



NumPy can help to vectorize your code, i.e. in this case that more of the looping is done in the C backend instead of in the Python interpreter. I would highly recommend to have a listen to the talk Losing your Loops: Fast Numerical Computing with NumPy by Jake VanderPlas. Although primarily tailored towards data science, it gives a good overview on the topic.



I did some slight modifications to your original script to include some of the vectorization ideas while still using your chosen Perlin noise library. (Sidenote: I changed the __ prefix to a single _, because that is the convention most Python programmers use for internal functions. See PEP8 style guide.)



# -*- coding: utf-8 -*-
from __future__ import division, print_function
import numpy as np
import noise
import timeit


class ST(object):
MAP_HEIGHT = 1800
MAP_WIDTH = 6400


def _noise(noise_x, noise_y, octaves=1, persistence=0.5, lacunarity=2.0):
"""
Generates and returns a noise value.

:param noise_x: The noise value of x
:param noise_y: The noise value of y
:return: numpy.float32
"""
if isinstance(noise_x, np.ndarray):
#rand_seed = np.random.randint(1, 9999, noise_x.size)
rand_seed = np.ones((noise_x.size, )) # just for comparison
value = np.array([noise.pnoise2(x, y, octaves, persistence, lacunarity, r)
for x, y, r in zip(noise_x.flat, noise_y.flat, rand_seed)])
return value.reshape(noise_x.shape)
else:
value = noise.pnoise2(noise_x, noise_y,
octaves, persistence, lacunarity,
1.0) # just for comparison
#np.random.randint(1, 9999))
return np.float32(value)


def _elevation_mapper(noise_x, noise_y):
"""
Finds and returns the elevation noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return _noise(noise_x, noise_y, 8, 0.9)


def _climate_mapper(y, noise_x, noise_y):
"""
Finds and returns the climate noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
# find distance from bottom of map and normalize to range [0, 1]
distance = np.sqrt((y - (ST.MAP_HEIGHT >> 1))**2) / ST.MAP_HEIGHT

value = _noise(noise_x, noise_y, 8, 0.7)

return (1.0 + value - distance) / 2.0


def _rainfall_mapper(noise_x, noise_y):
"""
Finds and returns the rainfall noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return _noise(noise_x, noise_y, 4, 0.65, 2.5)


def create_map_arr():
"""
This function creates the elevation, climate, and rainfall noise maps,
normalizes them to the range [0, 1], and then assigns them to their
appropriate attributes in the singleton ST.
"""
# assign noise map values
randomizer = np.random.uniform(0.0001, 0.9999)

start_arr = timeit.default_timer()

X, Y = np.mgrid[0:ST.MAP_WIDTH, 0:ST.MAP_HEIGHT]
noise_x = X / ST.MAP_WIDTH - randomizer
noise_y = Y / ST.MAP_HEIGHT - randomizer
elevation_arr_np = _elevation_mapper(noise_x, noise_y)
climate_arr_np = _climate_mapper(Y, noise_x, noise_y)
rainfall_arr_np = _rainfall_mapper(noise_x, noise_y)

duration_arr = timeit.default_timer() - start_arr

start_loop = timeit.default_timer()

elevation_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
climate_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
rainfall_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
for y in range(ST.MAP_HEIGHT):
for x in range(ST.MAP_WIDTH):
noise_x = x / ST.MAP_WIDTH - randomizer
noise_y = y / ST.MAP_HEIGHT - randomizer

elevation_arr[y, x] = _elevation_mapper(noise_x, noise_y)
climate_arr[y, x] = _climate_mapper(y, noise_x, noise_y)
rainfall_arr[y, x] = _rainfall_mapper(noise_x, noise_y)

duration_loop = timeit.default_timer() - start_loop

print(np.allclose(elevation_arr, elevation_arr_np.T))
print(np.allclose(climate_arr, climate_arr_np.T))
print(np.allclose(rainfall_arr, rainfall_arr_np.T))

print("GENERATION TIME: loop: {:.6f}, array: {:.6f}".format(duration_loop, duration_arr))

if __name__ == "__main__":
create_map_arr()


The bottleneck is still in



value = np.array([noise.pnoise2(x, y, octaves, persistence, lacunarity, r)
for x, y, r in zip(noise_x.flat, noise_y.flat, rand_seed)])


and it would be highly favorable to use an implementation which supports 2D input, preferably from NumPy, directly (see further reading below).



Nevertheless, the modifications bring the execution time down to a third of the original time on my machine (which is not that powerful):



True
True
True
GENERATION TIME: loop: 338.094228, array: 101.549388


Those three Trues are from a little test I added to check if the generated maps are the same within reasonable accuracy. For this purpose the additional random value in _noise was disabled.



Further reading



There have also already been similar questions on Code Review (see, e.g. here), where a reviewer created a Perlin noise implementation purely in Numpy. There also seems to be a GitHub project also doing Perlin noise with Numpy. So maybe have a look at them if your not forced to stick with noise.






share|improve this answer









$endgroup$













  • $begingroup$
    Thank you! I will take a look at the other ways to generate Perlin Noise. I was only using that library because it was the fastest I'd found so far.
    $endgroup$
    – LuminousNutria
    4 hours ago
















2












$begingroup$

Losing your Loops



Austin Hastings' comment gives you a good hint where to look at. The main takeaway for you should be:



(Most) loops are damn slow in Python. Especially multiple nested loops.



NumPy can help to vectorize your code, i.e. in this case that more of the looping is done in the C backend instead of in the Python interpreter. I would highly recommend to have a listen to the talk Losing your Loops: Fast Numerical Computing with NumPy by Jake VanderPlas. Although primarily tailored towards data science, it gives a good overview on the topic.



I did some slight modifications to your original script to include some of the vectorization ideas while still using your chosen Perlin noise library. (Sidenote: I changed the __ prefix to a single _, because that is the convention most Python programmers use for internal functions. See PEP8 style guide.)



# -*- coding: utf-8 -*-
from __future__ import division, print_function
import numpy as np
import noise
import timeit


class ST(object):
MAP_HEIGHT = 1800
MAP_WIDTH = 6400


def _noise(noise_x, noise_y, octaves=1, persistence=0.5, lacunarity=2.0):
"""
Generates and returns a noise value.

:param noise_x: The noise value of x
:param noise_y: The noise value of y
:return: numpy.float32
"""
if isinstance(noise_x, np.ndarray):
#rand_seed = np.random.randint(1, 9999, noise_x.size)
rand_seed = np.ones((noise_x.size, )) # just for comparison
value = np.array([noise.pnoise2(x, y, octaves, persistence, lacunarity, r)
for x, y, r in zip(noise_x.flat, noise_y.flat, rand_seed)])
return value.reshape(noise_x.shape)
else:
value = noise.pnoise2(noise_x, noise_y,
octaves, persistence, lacunarity,
1.0) # just for comparison
#np.random.randint(1, 9999))
return np.float32(value)


def _elevation_mapper(noise_x, noise_y):
"""
Finds and returns the elevation noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return _noise(noise_x, noise_y, 8, 0.9)


def _climate_mapper(y, noise_x, noise_y):
"""
Finds and returns the climate noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
# find distance from bottom of map and normalize to range [0, 1]
distance = np.sqrt((y - (ST.MAP_HEIGHT >> 1))**2) / ST.MAP_HEIGHT

value = _noise(noise_x, noise_y, 8, 0.7)

return (1.0 + value - distance) / 2.0


def _rainfall_mapper(noise_x, noise_y):
"""
Finds and returns the rainfall noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return _noise(noise_x, noise_y, 4, 0.65, 2.5)


def create_map_arr():
"""
This function creates the elevation, climate, and rainfall noise maps,
normalizes them to the range [0, 1], and then assigns them to their
appropriate attributes in the singleton ST.
"""
# assign noise map values
randomizer = np.random.uniform(0.0001, 0.9999)

start_arr = timeit.default_timer()

X, Y = np.mgrid[0:ST.MAP_WIDTH, 0:ST.MAP_HEIGHT]
noise_x = X / ST.MAP_WIDTH - randomizer
noise_y = Y / ST.MAP_HEIGHT - randomizer
elevation_arr_np = _elevation_mapper(noise_x, noise_y)
climate_arr_np = _climate_mapper(Y, noise_x, noise_y)
rainfall_arr_np = _rainfall_mapper(noise_x, noise_y)

duration_arr = timeit.default_timer() - start_arr

start_loop = timeit.default_timer()

elevation_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
climate_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
rainfall_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
for y in range(ST.MAP_HEIGHT):
for x in range(ST.MAP_WIDTH):
noise_x = x / ST.MAP_WIDTH - randomizer
noise_y = y / ST.MAP_HEIGHT - randomizer

elevation_arr[y, x] = _elevation_mapper(noise_x, noise_y)
climate_arr[y, x] = _climate_mapper(y, noise_x, noise_y)
rainfall_arr[y, x] = _rainfall_mapper(noise_x, noise_y)

duration_loop = timeit.default_timer() - start_loop

print(np.allclose(elevation_arr, elevation_arr_np.T))
print(np.allclose(climate_arr, climate_arr_np.T))
print(np.allclose(rainfall_arr, rainfall_arr_np.T))

print("GENERATION TIME: loop: {:.6f}, array: {:.6f}".format(duration_loop, duration_arr))

if __name__ == "__main__":
create_map_arr()


The bottleneck is still in



value = np.array([noise.pnoise2(x, y, octaves, persistence, lacunarity, r)
for x, y, r in zip(noise_x.flat, noise_y.flat, rand_seed)])


and it would be highly favorable to use an implementation which supports 2D input, preferably from NumPy, directly (see further reading below).



Nevertheless, the modifications bring the execution time down to a third of the original time on my machine (which is not that powerful):



True
True
True
GENERATION TIME: loop: 338.094228, array: 101.549388


Those three Trues are from a little test I added to check if the generated maps are the same within reasonable accuracy. For this purpose the additional random value in _noise was disabled.



Further reading



There have also already been similar questions on Code Review (see, e.g. here), where a reviewer created a Perlin noise implementation purely in Numpy. There also seems to be a GitHub project also doing Perlin noise with Numpy. So maybe have a look at them if your not forced to stick with noise.






share|improve this answer









$endgroup$













  • $begingroup$
    Thank you! I will take a look at the other ways to generate Perlin Noise. I was only using that library because it was the fastest I'd found so far.
    $endgroup$
    – LuminousNutria
    4 hours ago














2












2








2





$begingroup$

Losing your Loops



Austin Hastings' comment gives you a good hint where to look at. The main takeaway for you should be:



(Most) loops are damn slow in Python. Especially multiple nested loops.



NumPy can help to vectorize your code, i.e. in this case that more of the looping is done in the C backend instead of in the Python interpreter. I would highly recommend to have a listen to the talk Losing your Loops: Fast Numerical Computing with NumPy by Jake VanderPlas. Although primarily tailored towards data science, it gives a good overview on the topic.



I did some slight modifications to your original script to include some of the vectorization ideas while still using your chosen Perlin noise library. (Sidenote: I changed the __ prefix to a single _, because that is the convention most Python programmers use for internal functions. See PEP8 style guide.)



# -*- coding: utf-8 -*-
from __future__ import division, print_function
import numpy as np
import noise
import timeit


class ST(object):
MAP_HEIGHT = 1800
MAP_WIDTH = 6400


def _noise(noise_x, noise_y, octaves=1, persistence=0.5, lacunarity=2.0):
"""
Generates and returns a noise value.

:param noise_x: The noise value of x
:param noise_y: The noise value of y
:return: numpy.float32
"""
if isinstance(noise_x, np.ndarray):
#rand_seed = np.random.randint(1, 9999, noise_x.size)
rand_seed = np.ones((noise_x.size, )) # just for comparison
value = np.array([noise.pnoise2(x, y, octaves, persistence, lacunarity, r)
for x, y, r in zip(noise_x.flat, noise_y.flat, rand_seed)])
return value.reshape(noise_x.shape)
else:
value = noise.pnoise2(noise_x, noise_y,
octaves, persistence, lacunarity,
1.0) # just for comparison
#np.random.randint(1, 9999))
return np.float32(value)


def _elevation_mapper(noise_x, noise_y):
"""
Finds and returns the elevation noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return _noise(noise_x, noise_y, 8, 0.9)


def _climate_mapper(y, noise_x, noise_y):
"""
Finds and returns the climate noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
# find distance from bottom of map and normalize to range [0, 1]
distance = np.sqrt((y - (ST.MAP_HEIGHT >> 1))**2) / ST.MAP_HEIGHT

value = _noise(noise_x, noise_y, 8, 0.7)

return (1.0 + value - distance) / 2.0


def _rainfall_mapper(noise_x, noise_y):
"""
Finds and returns the rainfall noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return _noise(noise_x, noise_y, 4, 0.65, 2.5)


def create_map_arr():
"""
This function creates the elevation, climate, and rainfall noise maps,
normalizes them to the range [0, 1], and then assigns them to their
appropriate attributes in the singleton ST.
"""
# assign noise map values
randomizer = np.random.uniform(0.0001, 0.9999)

start_arr = timeit.default_timer()

X, Y = np.mgrid[0:ST.MAP_WIDTH, 0:ST.MAP_HEIGHT]
noise_x = X / ST.MAP_WIDTH - randomizer
noise_y = Y / ST.MAP_HEIGHT - randomizer
elevation_arr_np = _elevation_mapper(noise_x, noise_y)
climate_arr_np = _climate_mapper(Y, noise_x, noise_y)
rainfall_arr_np = _rainfall_mapper(noise_x, noise_y)

duration_arr = timeit.default_timer() - start_arr

start_loop = timeit.default_timer()

elevation_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
climate_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
rainfall_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
for y in range(ST.MAP_HEIGHT):
for x in range(ST.MAP_WIDTH):
noise_x = x / ST.MAP_WIDTH - randomizer
noise_y = y / ST.MAP_HEIGHT - randomizer

elevation_arr[y, x] = _elevation_mapper(noise_x, noise_y)
climate_arr[y, x] = _climate_mapper(y, noise_x, noise_y)
rainfall_arr[y, x] = _rainfall_mapper(noise_x, noise_y)

duration_loop = timeit.default_timer() - start_loop

print(np.allclose(elevation_arr, elevation_arr_np.T))
print(np.allclose(climate_arr, climate_arr_np.T))
print(np.allclose(rainfall_arr, rainfall_arr_np.T))

print("GENERATION TIME: loop: {:.6f}, array: {:.6f}".format(duration_loop, duration_arr))

if __name__ == "__main__":
create_map_arr()


The bottleneck is still in



value = np.array([noise.pnoise2(x, y, octaves, persistence, lacunarity, r)
for x, y, r in zip(noise_x.flat, noise_y.flat, rand_seed)])


and it would be highly favorable to use an implementation which supports 2D input, preferably from NumPy, directly (see further reading below).



Nevertheless, the modifications bring the execution time down to a third of the original time on my machine (which is not that powerful):



True
True
True
GENERATION TIME: loop: 338.094228, array: 101.549388


Those three Trues are from a little test I added to check if the generated maps are the same within reasonable accuracy. For this purpose the additional random value in _noise was disabled.



Further reading



There have also already been similar questions on Code Review (see, e.g. here), where a reviewer created a Perlin noise implementation purely in Numpy. There also seems to be a GitHub project also doing Perlin noise with Numpy. So maybe have a look at them if your not forced to stick with noise.






share|improve this answer









$endgroup$



Losing your Loops



Austin Hastings' comment gives you a good hint where to look at. The main takeaway for you should be:



(Most) loops are damn slow in Python. Especially multiple nested loops.



NumPy can help to vectorize your code, i.e. in this case that more of the looping is done in the C backend instead of in the Python interpreter. I would highly recommend to have a listen to the talk Losing your Loops: Fast Numerical Computing with NumPy by Jake VanderPlas. Although primarily tailored towards data science, it gives a good overview on the topic.



I did some slight modifications to your original script to include some of the vectorization ideas while still using your chosen Perlin noise library. (Sidenote: I changed the __ prefix to a single _, because that is the convention most Python programmers use for internal functions. See PEP8 style guide.)



# -*- coding: utf-8 -*-
from __future__ import division, print_function
import numpy as np
import noise
import timeit


class ST(object):
MAP_HEIGHT = 1800
MAP_WIDTH = 6400


def _noise(noise_x, noise_y, octaves=1, persistence=0.5, lacunarity=2.0):
"""
Generates and returns a noise value.

:param noise_x: The noise value of x
:param noise_y: The noise value of y
:return: numpy.float32
"""
if isinstance(noise_x, np.ndarray):
#rand_seed = np.random.randint(1, 9999, noise_x.size)
rand_seed = np.ones((noise_x.size, )) # just for comparison
value = np.array([noise.pnoise2(x, y, octaves, persistence, lacunarity, r)
for x, y, r in zip(noise_x.flat, noise_y.flat, rand_seed)])
return value.reshape(noise_x.shape)
else:
value = noise.pnoise2(noise_x, noise_y,
octaves, persistence, lacunarity,
1.0) # just for comparison
#np.random.randint(1, 9999))
return np.float32(value)


def _elevation_mapper(noise_x, noise_y):
"""
Finds and returns the elevation noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return _noise(noise_x, noise_y, 8, 0.9)


def _climate_mapper(y, noise_x, noise_y):
"""
Finds and returns the climate noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
# find distance from bottom of map and normalize to range [0, 1]
distance = np.sqrt((y - (ST.MAP_HEIGHT >> 1))**2) / ST.MAP_HEIGHT

value = _noise(noise_x, noise_y, 8, 0.7)

return (1.0 + value - distance) / 2.0


def _rainfall_mapper(noise_x, noise_y):
"""
Finds and returns the rainfall noise for the given noise_x and
noise_y parameters.

:param noise_x: noise_x = x / ST.MAP_WIDTH - randomizer
:param noise_y: noise_y = y / ST.MAP_HEIGHT - randomizer
:return: float
"""
return _noise(noise_x, noise_y, 4, 0.65, 2.5)


def create_map_arr():
"""
This function creates the elevation, climate, and rainfall noise maps,
normalizes them to the range [0, 1], and then assigns them to their
appropriate attributes in the singleton ST.
"""
# assign noise map values
randomizer = np.random.uniform(0.0001, 0.9999)

start_arr = timeit.default_timer()

X, Y = np.mgrid[0:ST.MAP_WIDTH, 0:ST.MAP_HEIGHT]
noise_x = X / ST.MAP_WIDTH - randomizer
noise_y = Y / ST.MAP_HEIGHT - randomizer
elevation_arr_np = _elevation_mapper(noise_x, noise_y)
climate_arr_np = _climate_mapper(Y, noise_x, noise_y)
rainfall_arr_np = _rainfall_mapper(noise_x, noise_y)

duration_arr = timeit.default_timer() - start_arr

start_loop = timeit.default_timer()

elevation_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
climate_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
rainfall_arr = np.zeros([ST.MAP_HEIGHT, ST.MAP_WIDTH], np.float32)
for y in range(ST.MAP_HEIGHT):
for x in range(ST.MAP_WIDTH):
noise_x = x / ST.MAP_WIDTH - randomizer
noise_y = y / ST.MAP_HEIGHT - randomizer

elevation_arr[y, x] = _elevation_mapper(noise_x, noise_y)
climate_arr[y, x] = _climate_mapper(y, noise_x, noise_y)
rainfall_arr[y, x] = _rainfall_mapper(noise_x, noise_y)

duration_loop = timeit.default_timer() - start_loop

print(np.allclose(elevation_arr, elevation_arr_np.T))
print(np.allclose(climate_arr, climate_arr_np.T))
print(np.allclose(rainfall_arr, rainfall_arr_np.T))

print("GENERATION TIME: loop: {:.6f}, array: {:.6f}".format(duration_loop, duration_arr))

if __name__ == "__main__":
create_map_arr()


The bottleneck is still in



value = np.array([noise.pnoise2(x, y, octaves, persistence, lacunarity, r)
for x, y, r in zip(noise_x.flat, noise_y.flat, rand_seed)])


and it would be highly favorable to use an implementation which supports 2D input, preferably from NumPy, directly (see further reading below).



Nevertheless, the modifications bring the execution time down to a third of the original time on my machine (which is not that powerful):



True
True
True
GENERATION TIME: loop: 338.094228, array: 101.549388


Those three Trues are from a little test I added to check if the generated maps are the same within reasonable accuracy. For this purpose the additional random value in _noise was disabled.



Further reading



There have also already been similar questions on Code Review (see, e.g. here), where a reviewer created a Perlin noise implementation purely in Numpy. There also seems to be a GitHub project also doing Perlin noise with Numpy. So maybe have a look at them if your not forced to stick with noise.







share|improve this answer












share|improve this answer



share|improve this answer










answered 5 hours ago









AlexAlex

653313




653313












  • $begingroup$
    Thank you! I will take a look at the other ways to generate Perlin Noise. I was only using that library because it was the fastest I'd found so far.
    $endgroup$
    – LuminousNutria
    4 hours ago


















  • $begingroup$
    Thank you! I will take a look at the other ways to generate Perlin Noise. I was only using that library because it was the fastest I'd found so far.
    $endgroup$
    – LuminousNutria
    4 hours ago
















$begingroup$
Thank you! I will take a look at the other ways to generate Perlin Noise. I was only using that library because it was the fastest I'd found so far.
$endgroup$
– LuminousNutria
4 hours ago




$begingroup$
Thank you! I will take a look at the other ways to generate Perlin Noise. I was only using that library because it was the fastest I'd found so far.
$endgroup$
– LuminousNutria
4 hours ago


















draft saved

draft discarded




















































Thanks for contributing an answer to Code Review Stack Exchange!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


Use MathJax to format equations. MathJax reference.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f216186%2fimproving-the-speed-of-creation-for-three-perlin-noise-maps-in-python%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

How to reconfigure Docker Trusted Registry 2.x.x to use CEPH FS mount instead of NFS and other traditional...

is 'sed' thread safe

How to make a Squid Proxy server?