Created
August 15, 2011 04:11
-
-
Save bemasher/1145700 to your computer and use it in GitHub Desktop.
Linear and concurrent julia set renderers in Google's Go.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
On an Intel Core 2 Duo E6600 @ 2.4Ghz the rendering portion took place in: | |
Linear = 24.107s | |
Concurrent = 9.063s | |
Both produce images whose SHA256 sum's are both: | |
3c68084ae742cfc7070e871ca721cb90694c28e886b77b4f289c8e84d18f124a |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"os" | |
"fmt" | |
"math" | |
"time" | |
"image" | |
"runtime" | |
"image/png" | |
) | |
const ( | |
// Complex parameter C for the julia set | |
C = complex(0.285, 0.01) | |
// Edge length for the image | |
DIM = 4096 | |
// Slice division: 2^4 = 16 | |
DIV = 4 | |
// Slice length: 4096 >> 4 = 256 | |
S_LEN = DIM >> DIV | |
// Will render a viewport of -1.25 to 1.25 on both axes | |
VIEW = 1.25 | |
// See comment in func init() | |
NCPUS = 16 | |
// Bailout limit so we don't get stuck in infinite loops | |
CUTOFF = 32 | |
// Where the image should be written | |
FILENAME = "julia_con.png" | |
) | |
// Contains a viewport to render and | |
// a subsection of the main image to render it onto | |
type Slice struct { | |
r image.Rectangle | |
img *image.Gray | |
} | |
// Rewrote this as cmath.Abs() is very slow | |
func Abs(c complex128) float64 { | |
return math.Sqrt(real(c)*real(c) + imag(c)*imag(c)) | |
} | |
// Initializes a slice of the image | |
func NewSlice(r image.Rectangle, img *image.Gray) Slice { | |
return Slice{r, img.SubImage(r).(*image.Gray)} | |
} | |
// Translate pixel coordinates into viewport coordinates | |
func translate(n int) float64 { | |
return float64(n) / DIM * (VIEW*2) - VIEW | |
} | |
// Determines the coloring a pixel should have based | |
// on whether or not the pixel exists in the julia set | |
func isJulia(x, y int) image.GrayColor { | |
z := complex(translate(x), translate(y)) | |
// Iterate until |z| < 4.0 or we reach | |
// reach the iteration cutoff | |
for i := 0; i < CUTOFF && Abs(z) < 2.0; i++ { | |
z = z * z + C | |
} | |
// We could return a binary value here 0 or 255 | |
// but i find that scaling the magnitude of z | |
// from 0 to 255 produces more interesting results | |
return image.GrayColor{uint8((Abs(z) / 2.0) * 255)} | |
} | |
// Renders the portion of the image described by the slice | |
func drawJulia(s Slice, done chan int) { | |
// For each pixel in the slice | |
for y := s.r.Min.Y; y < s.r.Max.Y; y++ { | |
for x := s.r.Min.X; x < s.r.Max.X; x++ { | |
// Set the color based on the result of | |
// the isJulia function | |
s.img.SetGray(x, y, isJulia(x, y)) | |
} | |
} | |
fmt.Printf("Rendered slice %+v\n", s.r) | |
done <- 1 | |
} | |
func init() { | |
// Until the go scheduler gets better you have to tell it how many threads | |
// it should execute goroutines on. I've found that specifying a reasonable | |
// maximum number of threads has no effect on performance as the OS's | |
// scheduler then takes care of what those threads do so I've set NCPUS to | |
// 16 as it will be fairly uncommon for consumer CPU's to have more than 8 | |
// cores or be capable of executing more than 16 threads at a time. | |
runtime.GOMAXPROCS(NCPUS) | |
} | |
func main() { | |
// Get current time for benchmark | |
start := time.Nanoseconds() | |
// Initialize image and channel for signalling | |
// that a slice is finished | |
julia := image.NewGray(DIM, DIM) | |
done := make(chan int) | |
// Create the number of slices specified with | |
// the DIV constant and start their rendering goroutines | |
for y := 0; y < DIM; y += S_LEN { | |
for x := 0; x < DIM; x += DIM >> DIV { | |
go drawJulia(NewSlice(image.Rect(x, y, x + S_LEN, y + S_LEN), julia), done) | |
} | |
} | |
// Block until all of the slices have | |
// completed rendering | |
for i := 0; i < 1 << DIV << DIV; i++ { | |
<-done | |
} | |
// Stop the benchmark as we're not interested in | |
// file I/O speed | |
stop := time.Nanoseconds() | |
// If the Rendered slice complete messages are enabled | |
// use this to make sure they all print before moving onto | |
// file writing stati | |
os.Stdout.Sync() | |
// Create or truncate the image file and | |
// queue file close with defer | |
file, err := os.Create(FILENAME) | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
defer file.Close() | |
// Let the user know we're starting to write the image | |
fmt.Printf("Writing %s\n", FILENAME) | |
png.Encode(file, julia) | |
// Let the user know we're done writing and the | |
// time it took to render the image | |
fmt.Printf("Done writing %s\n", FILENAME) | |
fmt.Printf("Rendered in %0.3fs\n", float64(stop - start) / 1000000000.0) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"os" | |
"fmt" | |
"math" | |
"time" | |
"image" | |
"runtime" | |
"image/png" | |
) | |
const ( | |
// See comment in func init() | |
NCPUS = 16 | |
// Bailout limit so we don't get stuck in infinite loops | |
CUTOFF = 32 | |
// Complex parameter C for the julia set | |
C = complex(0.285, 0.01) | |
// Edge length for the image | |
DIM = 4096 | |
// Will render a viewport of -1.25 to 1.25 on both axes | |
VIEW = 1.25 | |
// Where the image should be written | |
FILENAME = "julia_lin.png" | |
) | |
// Rewrote this as cmath.Abs() is very slow | |
func Abs(c complex128) float64 { | |
return math.Sqrt(real(c)*real(c) + imag(c)*imag(c)) | |
} | |
// Translate pixel coordinates into viewport coordinates | |
func translate(n int) float64 { | |
return float64(n) / DIM * (VIEW*2) - VIEW | |
} | |
// Determines the coloring a pixel should have based | |
// on whether or not the pixel exists in the julia set | |
func isJulia(x, y int) image.GrayColor { | |
z := complex(translate(x), translate(y)) | |
// Iterate until |z| < 4.0 or we reach | |
// reach the iteration cutoff | |
for i := 0; i < CUTOFF && Abs(z) < 2.0; i++ { | |
z = z * z + C | |
} | |
// We could return a binary value here 0 or 255 | |
// but i find that scaling the magnitude of z | |
// from 0 to 255 produces more interesting results | |
return image.GrayColor{uint8((Abs(z) / 2.0) * 255)} | |
} | |
// Renders entire image | |
func drawJulia(img *image.Gray) { | |
// For each pixel in the image | |
for y := 0; y < DIM; y++ { | |
for x := 0; x < DIM; x++ { | |
// Set the color based on the result of | |
// the isJulia function | |
img.SetGray(x, y, isJulia(x, y)) | |
} | |
} | |
} | |
func init() { | |
// Until the go scheduler gets better you have to tell it how many threads | |
// it should execute goroutines on. I've found that specifying a reasonable | |
// maximum number of threads has no effect on performance as the OS's | |
// scheduler then takes care of what those threads do so I've set NCPUS to | |
// 16 as it will be fairly uncommon for consumer CPU's to have more than 8 | |
// cores or be capable of executing more than 16 threads at a time. | |
runtime.GOMAXPROCS(NCPUS) | |
} | |
func main() { | |
// Get current time for benchmark | |
start := time.Nanoseconds() | |
// Initialize the image | |
julia := image.NewGray(DIM, DIM) | |
// Render the entire image | |
drawJulia(julia) | |
// Stop the benchmark as we're not interested in | |
// file I/O speed | |
stop := time.Nanoseconds() | |
// Create or truncate the image file and | |
// queue file close with defer | |
file, err := os.Create(FILENAME) | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
defer file.Close() | |
// Let the user know we're starting to write the image | |
fmt.Printf("Writing %s\n", FILENAME) | |
png.Encode(file, julia) | |
// Let the user know we're done writing and the | |
// time it took to render the image | |
fmt.Printf("Done writing %s\n", FILENAME) | |
fmt.Printf("Rendered in %0.3fs\n", float64(stop - start) / 1000000000.0) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment