Take the 2-minute tour ×
Mathematica Stack Exchange is a question and answer site for users of Mathematica. It's 100% free, no registration required.

I am experiencing some rather large performance decreases in Mathematica version 9.0 using the xkcd-styled plotting routines.

I had used the xkcdConvert code from Simon Woods as seen here (also described/annotated by Vitaliy Kaurov, as seen here) a few months back under version 8.0.4. This code worked great under version 8.0.4 and ran the following sample code in about 1.68 seconds:

f1[x_] := 5 + 50 (1 + Erf[x - 5]);
f2[x_] := 20 + 30 (1 - Erf[x - 5]);
xkcdConvert[
  Plot[{f1[x], f2[x]}, {x, 0, 10}, 
   Epilog -> 
    xkcdLabel /@ {{"Label 1", {1, f1[1]}, {1, 30}}, {"Label 2", {8, 
        f2[8]}, {0, 30}}}, 
   Ticks -> {{{3.5, "1st Event"}, {7, "2nd Event"}}, 
     Automatic}]] // AbsoluteTiming

This code will produce a simple xkcd-styled plot as given by Simon Woods example.

When I run the same code under version 9.0, the output takes ~17.0 seconds to produce!? I'm not sure why the performance degradation is happening in version 9.0.

I was hoping to produce a number of comic styled plots for a meeting. I have produced the desired plots, but all the plot-rendering took a really long time (one plot took ~8 minutes to render).

I welcome insight on how to improve the rendering performance under version 9.0 for future rendering efforts!

share|improve this question
3  
whaow... xkcd has become a fully supported functionality of mathematica ;-) –  chris Jan 4 '13 at 19:55
1  
Sadly, I have also noticed a speed decrease in much of my own code in Mathematica 9 vs Mathematica 8. Can't say I can really track it to anything specific .... –  Todd Allen Jan 4 '13 at 20:30
1  
It would be a good idea to go through the various steps one by one to see which specific function is causing the slowdown. Also check if the image size produced by Rasterize is the same in v8 and v9. –  Simon Woods Jan 5 '13 at 12:48
2  
For anybody who is keeping score, my benchmarks shows that Date and Time functions slowed down by about 10% between 8.04 and 9. –  Michael Stern Jan 6 '13 at 3:43
2  
@Joel Klein: I put together an extensive Date/Time benchmark in December, comparing Mathematica 8 to Mathematica 9 to VBA and Objective C. One of the date routines that slowed between Mathematica 8 and Mathematica 9 was Timing[ Do[DateDifference["11/15/1997", "12/1/1998", "Day"], {100000}]; ] –  Michael Stern Jan 7 '13 at 20:06

3 Answers 3

up vote 22 down vote accepted

By the power of CUDALink and a CUDA-enabled GPU, this code drastically increases the speed of xkcdDistort by almost 120x. It takes circa 60s to transform a 400 × 400 image using xkcdDistort on my laptop (i5-2410M + NVIDIA GT540M + 4GB memory), and CUDAxkcdDistort can do the same in just 0.5s. Distortion of a 1000 × 1000 image takes just 2.5s.

Needs["CUDALink`"]

CUDAxkcdDistort[p_, s_List, blockDim_Integer] :=
  Module[{
    code =
     "#include<math.h>
     __global__ void CUDATransform( float * in, float * out , float * \
aux , int width, int height, int channels, int distort ) 
     {
        int Index = threadIdx.x + blockIdx.x * blockDim.x ;
         int xIndex = Index % width ; 
        int yIndex = Index / width ;
         if ( xIndex >= width || yIndex >= height )
                return ;

         int xfrom , yfrom ;
         int xfetch , yfetch ;
         int from , to , ii;

        xfrom =  xIndex + yIndex * width ; 
         yfrom = ( width - 1 - xIndex ) + ( height - 1 - yIndex ) * width ;
         xfetch =  (int) ceil( xIndex + aux[xfrom] * distort ) ; 
         yfetch =  (int) ceil( yIndex + aux[yfrom] * distort ) ; 
         if ( xfetch > 0 && xfetch < width && yfetch > 0 && yfetch < height )
           {
             from = xfrom * channels;
             to = ( xfetch + yfetch * width ) * channels;
             for ( ii = 0 ; ii < channels ; ii++)
                out[ from + ii ] = in [ to + ii ];
           } 

     }", func, InMem, OutMem, AuxMem, width, height, pad = 15, 
    channels, distort = 20, result}, 
    func = CUDAFunctionLoad[code, "CUDATransform", {
       {"Float"},
      {"Float"},
      {"Float"},
      "Integer32",
      "Integer32",
      "Integer32",
      "Integer32"
      }, {blockDim, 1, 1}, TargetPrecision -> "Single"]; 
     If[ImageQ[p], InMem = ImagePad[p, pad, Padding -> White],
     InMem = ImagePad[Rasterize[p, ImageSize -> s], pad, Padding -> White]];
      {width, height} = ImageDimensions[InMem];
      channels = ImageChannels[InMem]; 
      InMem = CUDAMemoryLoad[Flatten[ImageData[InMem]], "Float"];
      OutMem = CUDAMemoryLoad[Flatten[ParallelTable[1, {i, height}, {j, width},{k,channels}]], "Float"];
      AuxMem = CUDAMemoryLoad[Flatten[ImageData[CUDAImageConvolve[RandomImage[{-1, 1},{width, height}], GaussianMatrix[10]]]], "Float"];
      CUDAMemoryCopyToDevice[InMem];
      CUDAMemoryCopyToDevice[OutMem];
      CUDAMemoryCopyToDevice[AuxMem];
      func[InMem, OutMem, AuxMem, width, height, channels, distort];result = ImagePad[Image[Partition[Partition[CUDAMemoryGet[OutMem], channels], width]], -3];
       CUDAMemoryUnload[InMem];
       CUDAMemoryUnload[OutMem];
       CUDAMemoryUnload[AuxMem];
       Return[ImageAdjust@GaussianFilter[result, 1]];];
       CUDAxkcdConvert[x_, s_List, blockDim_Integer] :=CUDAxkcdDistort[xkcdShow[x], s, blockDim];
share|improve this answer
    
Very nice timing on two counts! Just got a new laptop with a NVIDIA GeForce GT 650M graphics processor. Thank you!! –  Joseph Apr 9 '13 at 14:30
    
I take it that the s parameter contains the image dimensions. What about blockDim? –  Sjoerd C. de Vries Apr 10 '13 at 20:08

After some sleuthing through the xkcdConvert code I found the routine that is causing the execution slow down. The function containing the problem is the xkcdDistort function. See the following code:

 xkcdDistort[p_] := Module[{r, t, ix, iy},
   r = ImagePad[Rasterize@p, 10, Padding -> White];
   {ix, iy} = Table[RandomImage[{-1, 1},
                    ImageDimensions@r]~ImageConvolve~GaussianMatrix[10],{2}];
   ImagePad[ImageTransformation[r, # + 15 {ImageValue[ix, #], ImageValue[iy, #]}&,
   DataRange -> Full], -5]
 ];

Isolating the problem further, the real slow down is caused by the ImageValue function. The ImageValue function is used to generate distortion values on the raster image 'r' pixels, which are used within the ImageTransformation function. For some reason the ImageValue function has become very slow.

To support the statement about the ImageValue function's slow performance, I created a new xkcdDistort function, see following code:

xkcdDistort2[p_] := Module[{r, ix, iy, f},
   r = ImagePad[Rasterize@p, 5, Padding -> White];
   f[x_, y_] := {x + 2.1*Sin[0.18* x], y + 2.1*Sin[0.18 *y]};
   ImagePad[ImageTransformation[r, f @@ # &, DataRange -> Full], -2] 
  ];

This function is not as good at providing a cartoon like appearance, but it demonstrates that the ImageValue function is most likely causing the performance problem. Here is an example, which shows the performance differences on a Mac OSX computer running Mathematica 9.

 f1[x_] := 5 + 50 (1 + Erf[x - 5]);
 f2[x_] := 20 + 30 (1 - Erf[x - 5]);
 test1a = xkcdConvert[
          Plot[{f1[x], f2[x]}, {x, 0, 10}, 
              Epilog->xkcdLabel /@ {{"Label 1", {1, f1[1]}, {1, 30}}, 
                                    {"Label 2", {8, f2[8]}, {0, 30}}}, 
              Ticks -> {{{3.5, "1st Event"}, {7, "2nd Event"}}, Automatic}]
          ]//AbsoluteTiming

 test1b = xkcdConvert2[
          Plot[{f1[x], f2[x]}, {x, 0, 10}, 
              Epilog->xkcdLabel /@ {{"Label 1", {1, f1[1]}, {1, 30}}, 
                                    {"Label 2", {8, f2[8]}, {0, 30}}}, 
              Ticks -> {{{3.5, "1st Event"}, {7, "2nd Event"}}, Automatic}]
          ]//AbsoluteTiming

 GraphicsRow[{test1a[[2]], test1b[[2]]}, ImageSize -> Large]

This code will produce output with timing results. The original xkcdConvert function creates an image in 21.113965 seconds, while the new xkcdConvert2 function creates an image in 0.974976 seconds. image comparion

share|improve this answer
3  
+1 for the analysis - even though I still prefer making ugly plots by hand. –  Jens Jan 6 '13 at 6:30
1  
It's a shame that ImageValue is so much slower in v9. You might want to look at the code in Vitaliy's blog post, using BSplineFunction for the distortion. That will avoid the problem with ImageValue but keep the randomness of the distortion. –  Simon Woods Jan 6 '13 at 18:38
    
I took your suggestion on using Vitaliy's example and created a function using the BSplineFunction, but it seems to produce a blank image!? Not sure where I am going wrong... Any hints on using the BSPlineFunction is welcome! –  Joseph Jan 7 '13 at 2:52
    
ImageValue is actually pretty fast in v9, as one can see by querying for a million coordinate pairs at one go. What you have discovered is that it is not a recommended programming practice (when speed is a concern) to call a million times ImageValue with a single coordinate pair. –  Matthias Odisio Apr 9 '13 at 16:49

I had a look at an evaluation copy of v9 today, and did some digging around in ImageValue. It looks like the main cause of the slowdown is the handling of Span notation in the position specification. ImageValue calls Image`ImageDump`extractCoordinates with the requested position and the image dimensions as arguments. This function then does various checks and ultimately, for a numerical position argument, returns the position unchanged. So a somewhat clunky workaround is to temporarily redefine Image`ImageDump`extractCoordinates to simply return its first argument:

Internal`InheritedBlock[{Image`ImageDump`extractCoordinates},
 Unprotect[Image`ImageDump`extractCoordinates];
 Image`ImageDump`extractCoordinates = #1 &;
 xkcdConvert[plot]]

This is much faster, though still not as fast as v8.

Probably the best solution is to use the idea from Vitaliy's blog post, using a BSplineFunction to define the distortion. This is way faster than my code. A drop-in replacement for xkcdDistort and xkcdConvert with optional amplitude and frequency arguments is:

xkcdDistort[p_, am_, fr_] := Module[{r, f},
   r = ImagePad[Rasterize@p, 10, Padding -> "Fixed"];
   f = BSplineFunction[Table[
      {x + (am/fr)*RandomReal[{-1, 1}], 
       y + (am/fr)*RandomReal[{-1, 1}]},
      {x, 0, 1, 1./fr}, {y, 0, 1, 1./fr}], 2, 
     Method -> {"Extrapolation" -> "Repeat"}];
   ImageTransformation[r, f @@ # &, ImageDimensions[r],
    DataRange -> {{0, 1}, {0, 1}}, Padding -> "Fixed"]];

xkcdConvert[x_, am_: 0.3, fr_: 50] := xkcdDistort[xkcdShow[x], am, fr];
share|improve this answer
    
This is perfect! Just what I was looking for... On my v9 system your new solution is ~3.6 times faster than the original xkcdConvert on a v8 system (comparison done on nearly the same hardware, just different Mathematica versions) –  Joseph Jan 7 '13 at 22:19

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.