Tuesday, June 11, 2013

Web Host Shootout: Finding the Best (and Cheapest) Web Host

I just spent about $1,000 testing 7 different host providers. Thankfully, (almost) all of it is getting refunded. I'm in the process of completely redoing this website and decided I needed more power to make it how I really want it. Blogger doesn't provide the features I want, so I started looking for a proper hosting service so I could host my own website. I tested several hosts in an effort to find the best "bang for the buck."

How I tested

First impressions are everything. I didn't do any kind of extended testing. I looked for something that was awesome from the start. I didn't want to learn to love my host after several months of use. I wanted to love them today.

With this in mind, I focused on server ping time, server response time, download bitrate, max file upload size in a WordPress blog, and how well things "just worked." I didn't want to spend more than $10/month (trying to stick close to $5/month), which makes things tricky because in this world, you typically get what you pay for (if you pay very little, you get very little).

I analyzed things from different homes/offices (and different ISP companies) across a period of two days. I tested using curl from the command line to check raw download speeds (redirected to /dev/null) and Chrome's developer tools (for network response times and other network protocol information), as well as Google's Page Speed Insights. I tried to optimize all pages for the highest Page Speed index score (enabling compression and caching on files), as well as overall server response time and download bitrate (from a clean cache). I tested each host repeatedly, averaging results (after discarding outliers). All tests were run with a WordPress blog serving a single post entry (same theme, same blog entry, same settings). In reality, this test was dead simple and doesn't give an in-depth analysis, but it works for showing what kind of basic features your host provides. After all, all the other features are built on top of these basic features.

Without any more words, I'll show the results!

Namecheap

  • Price: $3.45/mo for 2 years
  • Max WordPress upload size (default): 32MB
  • Down: ~4765kBps
  • Ping: ~53ms
  • Server response time: 475-675ms (averaged about 600ms)
  • Other thoughts: Overall, decent. Response times could be better, but download speeds were great. I use them for my domain purchases, so I figured I'd try them for web hosting too.

SiteGround

  • Price: $3.95/mo for 3 years
  • Max WordPress upload size (default): 24MB
  • Down: ~800kBps
  • Ping: ~50ms
  • Server response time: 275-600ms (averaged about 300ms)
  • Other thoughts: Pretty good response times... very slow download speeds. Awesome customer service (who also tried to talk me out of canceling, but that wasn't too bad).

Omnis

  • Price: $5.95/mo for 2 years
  • Max WordPress upload size (default): 32MB
  • Down: ~4469kBps
  • Ping: unpingable
  • Server response time: 400-800ms (averaged about 600ms)
  • Other thoughts: From the start I was hesitant. The checkout process was buggy and kept restarting things and making me re-enter things. The install scripts sucked. When the install script asked if I wanted to host the WordPress site on example.com or www.example.com (i.e. with or without the "www" subdomain), I selected without the "www" subdomain. After the install script ran, I found it appended "empty" to my domain name because I had left the subdomain empty (literally, it had set up all my hosting options and now thought my domain was emptyexample.com instead of example.com). I had to redo it with the "www" subdomain set. Canceling the service went fine, but I was still charged $9.95 for the domain (which I expected) and $11.88 for WHOIS privacy (which I think is overpriced for such a simple service). Overall, I wasn't impressed.

BlueHost

  • Price: $4.95/mo for 3 years
  • Max WordPress upload size (default): 10MB
  • Down: ~1637kBps
  • Ping: ~38ms
  • Server response time: 550ms-850ms (averaged about 700ms)
  • Other thoughts: Refunding went very well. Full refund for everything! I was expecting to have to pay for the domain name and not be able to refund it.

DreamHost

  • Price: $8.95/mo for 3 years
  • Max WordPress upload size (default): 7MB
  • Down: ~2051kBps
  • Ping: ~85ms
  • Server response time: 660-880ms (average: 700ms)
  • Other thoughts: No cPanel (uses their own panel). Incredibly disappointing max file size (was able to increase it to 20MB following these instructions (could not get it higher than 20MB without doing more fiddling that I didn't care to do)). I'm not in love with cPanel, but having a custom panel means now I have to relearn everything (unlike other webhosts, where you can get up and running in no time since the panel is familiar). Confirmation emails were flagged as spam and I never got a receipt email. No webchat for support. I was disappointed after having higher expectations (from all the good things I've heard about them).

HostGator

  • Price: $3.96/mo for 3 years
  • Max WordPress upload size (default): N/A
  • Down: N/A
  • Ping: N/A
  • Server response time: N/A
  • Other thoughts: 12 hours after registering, I got an email saying I needed to call them to verify the account. At this point, I was done with them. The "just works" factor is very important to me. Also, I hate using my phone.

DigitalOcean

  • Price: $5/mo (no long term commitment) (also can be less, as it's really billed hourly and you can control the hours)
  • Max WordPress upload size (default): 2MB
  • Down: ~3893kBps
  • Ping: ~47ms
  • Server response time: 150-200ms (averaged 160ms)
  • Other thoughts: I was easily able to increase the WordPress file size limit to 50MB. Root access! VPS! They don't have "unlimited" disk space or bandwidth like the other hosts do (20GB disk space (on an SSD!) and 1TB bandwidth), but then again, my website is so small that these "limitations" are, realistically, as good as unlimited to me. I'll never use all 20GB of disk space or a full terabyte of bandwidth.

Conclusion

I almost never found DigitalOcean. I wasn't looking at VPS hosting because it's usually a lot more than shared hosting. But wow, am I glad I googled "vps hosting." I almost tried Virpus and DirectSpace, but they don't offer SSDs and I highly doubt they can beat an average 160ms server response time. If you want something for less than $5/month, I'd look at them.

DigitalOcean completely annihilates the competition, in my book. They absolutely nail the "just works" factor (their UI is the most gorgeous of the other hosts, and it conveys a professional "just works" attitude). Spinning up a new VPS with my selected Linux flavor took less than a minute (and was incredibly easy). Rebooting Ubuntu took less than 5 seconds. Server response times are amazing (only about 60ms longer than Google's home page response time, in my testing, and Google has an army of servers). Download speeds are great (usually hits >5MBps after ramping up). The OS took about 1GB, leaving 19GB of free space to use. Root access is awesome to have, and now I'm not sure I can ever have a server without it (not that I need truly need it; the geek in me loves it and the ability to tweak with things exactly how I want them). No long term commitment means I can always switch to another host if I decide (without having years left on a prepaid service). I do wish they had HDD mass storage available in combination with the SSD, but I'm perfectly happy with them. Seriously, after refreshing the page with a clean cache, my page loaded instantly. I thought it loaded everything from cache it loaded so fast (and had to double check that it didn't).

As of this moment, I have canceled all of my other hosting accounts and am now moving everything to DigitalOcean. They have totally won my heart.

Thursday, May 30, 2013

What happens when Matt Might links to your website

I just crossed the 3,000-page-views-in-a-single-month barrier.

In March, Matt Might linked to my website (thanks Matt!). I was making pretty steady growth in my monthly number of hits on my site, just breaking 1,000 hits a month in February, 2013. Then in the beginning of March, Matt Might linked to my site and this happened:


Yeah, it says "March," but this is really February's stats. Just barely broke 1,000!


Matt Might linked to my site the beginning of March and more than doubled my hit count!

And here are today's stats, a couple months later:


Almost there...


And a few minutes later, over 3,000! (next goal: getting over 9000)

If you're curious, I now get about (on a weekly basis) ~130 visitors from Matt, ~110 visitors from Google (~55 from US and ~55 from non-US, like UK, Korea, Germany, India, Hong Kong, etc.), ~1 from the SFML forums, ~1 from the GameDev.net forums, and every once in a while, 1 from StackOverflow. Crazy! This also means that on average, each visitor visits 2-3 pages. Anyway, back to focusing on posting content this site was actually made for (this site is meant to be a portfolio rather than a blog, so I'll post less silly content now).

Wednesday, May 22, 2013

GIF: "gif" vs "jif"

I never realized how many people say "gif" instead of "jif" (go head, click a letter), and while I personally don't care a lot, I have to say something about the argument for "gif."

Most of the arguments I've heard/read in favor of "gif" argue that it's Graphics Interchange Format and not Jraphics Interchange Format. "jif" people might respond that giraffe has a soft G, suggesting that GIF should follow suit. "gif" people might then respond that "giraffe" comes from French, and as such shouldn't be a model for pronouncing GIF.

But let's go back to that first argument of the "gif" people: that the G should be pronounced like it is in graphics (because that's what it stands for, after all). If that is the logic to which you submit, I have compiled a small list of other acronyms for which I hope you also apply the same logic:

  • laser: should be pronounced "la-seer"
  • scuba: should be pronounced "skuh-ba"
  • ASAP: should be pronounced "a-sap"
  • BASIC: should be pronounced "bah-sik"
  • Perl: should be pronounced "peh-rl"
  • SWAT: should be pronounced "swat" (not "swot")
  • FIFO: should be pronounced "fih-foh" (not "fahy-foh")

The list could go on, but hopefully that's enough to at least show that the argument that GIF should be "gif" because the G is for "graphics" is a weak argument at best.

FYI, this little "rant" has less to do with "gif" vs "jif" and more to do with me attempting to humorously (yet validly) pointing out a weak argument/justification.

Saturday, May 4, 2013

Test Your YouTube Internet Speed

I'm pretty skeptical about Internet speeds. ISPs usually only guarantee you an "up to" speed, like "up to 50 Mbps." This kind of upper bound/maximum promise is, in my opinion, useless. They could promise you speeds "up to 99999 Mbps" and then only give you 1 Mbps (or less), and technically they have fulfilled their promise of giving you "up to 99999 Mbps" (think about it: the only thing they're promising is that they won't give you more than 99999 Mbps, so as long as they give you less than 99999 Mbps, they're keeping the promise). Comcast wouldn't give me any kind of guaranteed minimum or average speeds when I asked them, so I like to test my speeds occasionally to know what they're really giving me. And for good reason:


Sure, my average might be decent, but it's like a roller coaster with lots of disappointing low speeds. I'd prefer a consistent medium speed than a bipolar roller coaster.

Testing YouTube

I just learned about YouTube's speed testing. When you watch videos signed in to your YouTube/Google account, they keep track of how fast your connection is. So go to this page to test your YouTube speed. Near the bottom of that page, you can watch a test video to test your speed in real time. Play the test video. The instructions to 'look next to "Streaming HTTP"' are misleading, because "Streaming HTTP" appears nowhere on the page. Here's how to do it:

  1. Go to YouTube's speed testing page.
  2. Look at your charts to your hearts content.
  3. Click the "Show Test Video" link to show the test video.
  4. Right click on the video while it's playing and select "Show video info".
  5. Look at the statistics by "TagStreamPlayer, HTTP" to see what your kbps are (1000 kbps = 1 Mbps).
  6. If it says "0 kbps", click on the little cog wheel in the video and change the video quality (to anything). If it's saying 0 kbps but the video is playing, it needs help in refreshing its calculations, and by changing the video quality it refreshes things so that the real kbps should be showing.
  7. Change the video quality to whatever you want; I like to test on the highest quality.

The cool part? If you watch any video, you can right click on the video and "Show video info" to get these stats. You can also right click on a video and select "Take speed test" which will take you to this page.

Why not just a normal speed test?

There are several ways to test your Internet speed, but good speeds on one website don't mean good speeds on another website. Whether this is because of "clogged tubes," throttling, a busy server, slooow pings, or any number of other potential issues, is a mystery (unless you take the time to solve it). But if you want to know your speeds on YouTube... well, you might as well use YouTube itself to test.

Thursday, March 21, 2013

WebGL: Fixing "INVALID_OPERATION: drawArrays: attribs not setup correctly"

I kept getting the WebGL: INVALID_OPERATION: drawArrays: attribs not setup correctly error in my simple WebGL app, which wasn't very helpful to me. Ok, I get it's not setup correctly, but what could be wrong? It turns out the problem was being caused by me using two different shader programs, with each program having a different number of attributes. I want to walk through this clearly, because most of the things I found online when searching for solutions were... lacking, to put it kindly. Let's look at my shaders:

Shader Program #1

It's a pretty simple program. Given a set of 2D vertices, make them all white.

Vertex shader:
attribute vec2 vertex;

void main(void)
{
    gl_Position = vec4(vertex, 0.0, 1.0);
}
Fragment shader:
precision mediump float;

void main(void)
{
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}

Shader Program #2

Not too complicated. Given a set of 2D vertices and corresponding texture coordinates, render a textured object.

Vertex shader:
attribute vec2 vertex;
attribute vec2 textureCoord;

varying vec2 fragTextureCoord;

void main(void)
{
    gl_Position = vec4(vertex, 0.0, 1.0);
    fragTextureCoord = textureCoord;
}
Fragment shader:
precision mediump float;

uniform sampler2D sampler;

varying vec2 fragTextureCoord;

void main(void)
{
    gl_FragColor = texture2D(sampler, fragTextureCoord);
}

The problem

The problem is rooted in the shaders. The first vertex shader only has one attribute (vertex), while the second vertex shader has two attributes (vertex and textureCoord). When switching from one program to another, the enabled attributes weren't getting switched! In other words, when I loaded shader program #1 and called enableVertexAttribArray to enable the vertex attribute, and then later when I loaded shader program #2 and called enableVertexAttribArray to enable the vertex and textureCoord attributes, the enabling of the attributes isn't bound to a specific program. Enabling an attribute is a global operation, regardless of whether or not you create or use a different program. So when using shader program #1, the textureCoord attribute was still enabled! This resulted in an error (which seems sensible). There are two ways to work around this.

Solution #1: disableVertexAttribArray

One option is to just disable the extra attributes. When we use shader program #1, we can call disableVertexAttribArray to disable the extra attributes that shader program #2 uses. You have to watch out a little if you do this, because if you aren't explicitly assigning index numbers to your attributes, it's possible the vertex attribute in shader programs #1 and #2 will have different index values. One way to do this is to track which program you were using and which program you're going to use now, and then disable any extra attributes (or enable any missing attributes). Something like:

function switchPrograms(currentProgram, newProgram)
{
    // Gets the number of attributes in the current and new programs
    var currentAttributes = gl.getProgramParameter(currentProgram, gl.ACTIVE_ATTRIBUTES);
    var newAttributes = gl.getProgramParameter(newProgram, gl.ACTIVE_ATTRIBUTES);

    // Fortunately, in OpenGL, attribute index values are always assigned in the
    // range [0, ..., NUMBER_OF_VERTEX_ATTRIBUTES - 1], so we can use that to
    // enable or disable attributes
    if (newAttributes > currentAttributes) // We need to enable the missing attributes
    {
        for (var i = currentAttributes; i < newAttributes; i++)
        {
            gl.enableVertexAttribArray(i);
        }
    }
    else if (newAttributes < currentAttributes) // We need to disable the extra attributes
    {
        for (var i = newAttributes; i < currentAttributes; i++)
        {
            gl.disableVertexAttribArray(i);
        }
    }

    // With all the attributes now enabled/disabled as they need to be, let's switch!
    gl.useProgram(newProgram);
}

Note that the above solution assumes all your attributes are array attributes. If you have an attribute that isn't an array (i.e. it shouldn't be enabled with enableVertexAttribArray), you'll have to come up with a more robust solution. Hopefully the above at least gives you some ideas and can help point you in the right direction in your quest to come up with a valid solution.

Solution #2: vertexAttribPointer

Enable all the attributes! Alright, so the whole reason we were getting this INVALID_OPERATION error comes from here (I suggest you read it and try to understand it!). The key here is in the last statement: "If a vertex attribute is enabled as an array, a buffer is bound to that attribute, but the attribute is not consumed by the current program, then regardless of the size of the bound buffer, it will not cause any error to be generated during a call to drawArrays or drawElements."

This means that if your program doesn't use the attribute, it's okay to fill it with a tiny junk array (note: not null, as that has other implications), as it won't actually be used. This means you can bind a tiny junk buffer with bindBuffer and then set the superfluous attribute to that tiny junk buffer with vertexAttribPointer, and then continue on your merry way. All WebGL wants is for your enabled array attributes to be associated with some valid (non-null) array (which is why you get the INVALID_OPERATION error if they're not). If you enable all the attributes, but your program doesn't actually use an enabled attribute, WebGL doesn't care how big the array is that's associated with it (so you won't get any out-of-bounds errors). It's just happy it has an array associated with the enabled attribute.

Saturday, March 2, 2013

ASCII Snake

I've always thought the classical game of Snake is a fun game to program. One day I decided I wanted to speed-code a game of Snake, rendered entirely with (extended) ASCII art. One particular friend of mine made feature requests and gave me feedback as I quickly developed it.

In the end, the game was quite a bit of fun. It ran in a Windows console, had color, cheats, Easter eggs, and even a multiplayer mode (that operated kind of like a Tron Lightcycle battle). My friend and I made several good memories with this little game, competing for the high score and battling it out in multiplayer. If everything was quiet and a sudden "Dang it!" came echoing from another room, I knew a game of Snake was going down.

Because I was speed coding this, I'm not going to release the code (it's atrocious!). I will, however, for kicks and giggles, provide binaries which you may play with. You can download it here. See if you can figure out all the cheats and Easter eggs!

Haskell Mandelbrot

I wrote a program (years ago) in Haskell that generated the image above of the Mandelbrot set. I really like Haskell, but I haven't spent enough time with it and never really wrapped my head around monads. I'd like to dive back into it when I get the time.

Anyway, the program I used to generate the fractal is below. I'm not a Haskell guru, so if the code is an abomination and you know how to improve it, please share in the comments! It would be cool to improve my Haskell skills. I wrote this years ago:

module Main where

import Data.Complex
import Data.Char
import qualified Data.ByteString.Char8 as C -- For file output

-- Constants
maxIter :: Int -- Max iterations
maxIter = 750

width  :: Int -- Image width
height :: Int -- Image height

width  = 400
height = 400

-- Note: aspect ratio of (minX, minY), (maxX, maxY) must
-- match aspect ratio of (width, height)
minX :: Double -- Min x-coordinate of graph
maxX :: Double -- Max x-coordinate of graph
minY :: Double -- Min y-coordinate of graph
maxY :: Double -- Max y-coordinate of graph

-- For the zoomed in part of the Mandelbrot:
--minX = -0.826341244461360116
--maxX = -0.8026423086165848822

--minY = -0.2167936114403439588
--maxY = -0.193094675595568725

--For a full view of the mandelbrot
minX = -2.5
maxX = 1.5

minY = -2
maxY = 2


-- The actual fractal part
-- It basically works on a matrix, which we will call M, that represents a grid of
-- points on the graph. Essentially, M[i, j] is (xList[j], yList[i])
xList :: [Double]
yList :: [Double]

xList = [minX, (minX + ((maxX - minX) / (fromIntegral width  - 1)))..maxX]
yList = reverse [minY,(minY + ((maxY - minY) / (fromIntegral height - 1)))..maxY]

row :: Double -> C.ByteString -- A row of image bytes for a given y-coordinate
row y = C.pack [frac (x :+ y) (0 :+ 0) 0 | x <- xList] -- For Mandelbrot set
--row y = C.pack [frac ((-0.1) :+ (0.8)) (x :+ y) 0 | x <- xList] -- For Julia set

etaFraction :: Complex Double -> Double
etaFraction z = (log (log (magnitude z))) / log 2

smoothEta :: Int -> Complex Double -> Double -- Smooth escape time algorithm value
smoothEta iter z = (fromIntegral iter - etaFraction z) / fromIntegral maxIter

color :: Int -> Complex Double -> Double -- Gets the color for the point, in range [0, 1]
color iter z = 1 - smoothEta iter z -- Smooth escape time algorithm (and invert)
--color iter z = fromIntegral iter / fromIntegral maxIter

interpolate :: Double -> Char -- Adds an interpolation curve for interpolating color
interpolate v = chr (truncate ((v ^ 12) * 255)) -- Polynomial curve
--interpolate v = chr (truncate(v * 255)) -- Linear

frac :: Complex Double -> Complex Double -> Int -> Char -- The actual fractal algorithm!
frac c z iter
     | iter >= maxIter = chr 255 -- never escaped, return color value of 255
     | otherwise = let z' = z * z + c
               in if ((realPart z') * (realPart z') + (imagPart z') * (imagPart z')) > 4
                  then interpolate (color iter z')
    else frac c z' (iter + 1)


-- The file output
pgmHeader :: C.ByteString
pgmHeader = C.pack ("P5\n" ++ (show width) ++ " " ++ (show height) ++ "\n255\n")
main = C.writeFile "fractal.pgm" (C.append pgmHeader (C.concat [(row y) | y <- yList]))

And some pictures from this program (each image has had some constants tweaked):


The Mandelbrot set in pure black and white; only pixels considered "inside" the set are black.


A Julia set based on (0.285, -0.01i). I actually edited the brightness/contrast in post-processing on this one to really bring out the spirals.


A zoomed in portion of the Mandelbrot set. I edited this one's brightness/contrast settings in post processing to bring out some of the little details.