*Coder Blog

Life, Technology, and Meteorology

Month: November 2012

The Surf Bike

Marlinspike’s blog entry “The Worst” is a good read, and calls to mind some lessons learned while I was earning my degree at UCSB.

On the UCSB campus, everyone bikes…and I mean everyone. I would guess there is around 20,000 bikes on campus, to the point where biking anywhere means navigating a ton of traffic. While I spent my time there, I had 5 different bikes.

The first two were bikes from childhood that I used during my Freshman year. I put so many miles on them, that eventually even after repairing parts that broke, they were pretty well worn out to the point that I needed a new bike.

So I bought my third bike, a Raleigh something or other. It was a pretty sharp looking bike. Nothing overly expensive, but nice enough that it was stolen about a year after I bought it. Having it stolen broke my heart, because I made sure to always lock it with one of those U-locks, and it was taken from the bike racks just outside my dorm room.

I decided from then on out to never trust bike locks. My fourth bike was a Trek, and it was the first bike I had that let me really get into mountain biking (which I still enjoy as a hobby today). It was more expensive than any of my other bikes, and for that reason, I never locked it up anywhere. I stored it in my dorm room (and later inside my apartment) when I was at home. On campus, I worked in the Engineering building, so I was able to bike to work and park the bike in my office there, just walking to the rest of my classes. It worked out pretty well, but as Marlinspike would say, the bike owned me.

Then about halfway into my Junior year a bike showed up on the back patio of our apartment. It was at least 20-30 years old, and half rusted out. It was the ugliest damn bike I have ever seen. To this day, we have no idea where it came from. We left it there for a couple of weeks, to see if anyone would find it and reclaim their property. Nobody did, so I moved the bike around to the front of our apartment and parked it in the bike rack. No lock, nothing.

The bike became our apartment’s “surf bike”, because it was perfect for when we wanted to go out surfing. There weren’t bike racks to use at our local surf spots, so usually we had to spend a lot of time walking to the ocean. With the surf bike, we didn’t need to lock it up, so we just took it to the beach and left it there while we were out, and rode it back when we were done. It was liberating.

I really started to enjoy the care-free attitude of the surf bike, so a few months later I started to use it as my daily ride too. For over a year, I rode it to campus and back every day, never locking it anywhere, and nobody ever took it. There were a few squeaks in the gearing, but it never broke down on me. It really was the perfect college bike.

I used the bike all the way through the end of senior year. When it was time to move home for the summer, it didn’t feel right to take it with. So we left it there, for the next fortunate person to discover and love.

DSC_0451
Photo by Ryon Edwards

Overhead while using GCD

Today I spent some time optimizing the Particle Mode simulation code in Seasonality Core. While doing some measurements, I discovered that quite a bit of time was spent in GCD code while starting new tasks. I use dispatch_apply to iterate through the particles and run the position and color calculations for the next frame. In the tests below, I was simulating approximately 200,000 particles on the Macs, and 11,000 particles on the iPad.

I decided to try breaking the tasks up into fewer blocks, and run the dispatch_apply for groups of around 50 particles instead of running it for each particle. After making this change, the simulation ran in up to 59% less CPU time than before. Here are some informal numbers, just by looking at Activity Monitor and roughly estimating:

CPU Usage
Device   Before   After   Time Savings
Mac Pro (2009, Oct 2.26Ghz Xeon)   390%   160%   59%
Retina MBP (2012, Quad 2.6Ghz i7)   110%   90%   18%
MacBook Air (2011, Duo 1.8Ghz i7)   130%   110%   15%
 
iPad 3 (fewer particles)   85%   85%   0%

As you can see, the benefits from the new code running on the Mac Pro are substantial. In my earlier code, I was somewhat suspicious of why the simulation took so many more resources on the Mac Pro than on the laptops. Clearly the overhead in thread creation was a lot higher on the older Xeon CPU. This brings the Mac Pro’s processing times closer to what the other more modern processors can accomplish.

Perhaps an even more surprising result is the lack of a speedup on the iPad. While measuring both runs, the two versions averaged about the same usage. Perhaps if I had a more formal way to measure the processing time, a small difference might become apparent, but overall the difference was minimal. I’m guessing that Apple has built logic into the A-series CPUs that allows for a near 0 cost in context switching. Makes you wonder how much quicker something like this would run if Apple built their own desktop-class CPUs.

Race to the Bottom

A few years ago, developers of mobile apps were on a “race to the bottom.” When the iPhone App Store opened, apps were priced reasonably, but in the months that followed developers dropped prices to remain competitive until the most popular price point became just $0.99.

Today I read this article on Engadget about Amazon and Google selling their latest tablets at or below cost. Sounds familiar…

Honestly, I think the only reason Google and Amazon are doing so well in the tablet market is because they have this no-profit hardware model. Apple is the big player here, and has always priced their products to have a healthy hardware profit margin. There were several earlier Android-based tablets before the Nexus series and the Kindle Fire, all priced in the same ballpark as the iPad. Unfortunately (for these other companies), customers decided that at the same price point they would rather have an iOS device.

Amazon realized the only way they could break into the market would be to sell at cost, and make up the profits when customers purchased content to read or watch. Since Amazon also sells that same content, they are in a good position to turn a profit using this model.

The problem is consumers are now expecting all hardware to be sold at this lower price. Just look at the reaction to Apple’s recent $329 starting price of the iPad Mini. The Mini’s starting price is $80-130 more expensive than Android-based competitors, and people have had something to say about it. In reality, Apple is just pricing it in a way where it can sustain their business.

I don’t place the blame on consumers for this attitude. The latest mobile craze has people buying the newest models year after year. Corporations like Apple and Google are pushing for this consumerism attitude, so they can continue to sell record numbers of devices and maximize profit. The thing is, many people don’t need and can’t afford to buy the latest tablet/phone/etc year after year (at least not at prices with a healthy profit margin). So consumers continue to pressure the corporations to sell at lower prices. In this respect, this consumerism attitude the corporations have pushed has backfired on them.

The tablet market is still young. Last year, I thought Apple had such a big lead with the iPad, that it would be very difficult for other platforms to catch up. But a lot has happened in the last 12 months. It should be interesting to see just how this plays out in the long term.

Living in a Sandboxed World

No matter what your view on Apple’s new sandboxing requirement for the Mac App Store, if you want to keep updating your MAS apps, you’re going to need to sandbox them. I was able to sandbox Seasonality Core pretty easily. I don’t access any files outside of Application Support and the prefs file. My entitlements just require outgoing network connections, which was pretty easy to enable in the target settings.

However, I distribute two versions of Seasonality Core. One is the Mac App Store application, and the other is a version for my pre-Mac App Store customers. The question arose: should I sandbox the non-Mac App Store application? I wanted the answer to this question to be yes, but unfortunately the serial number licensing framework I am using kept me from doing this. So I was forced to sandbox the Mac App Store version, but keep the non-Mac App Store version outside the sandbox. Crap.

You might be wondering what the big deal is here. Can’t my Mac App Store customers just use one app, and pre-Mac App Store customers use the other one? Well, yes, but there are a few situations where some customers might use both versions of the app.

If someone uses both the Mac App Store version and the non-Mac App Store version, things go south quickly. The first time the sandboxed Mac App Store version is run, all of Seasonality Core’s data files will be migrated into the sandbox. That means the next time the non-Mac App Store version is opened, it won’t be able to see any of the past data Seasonality Core has collected. That’s not good.

So how did I get around this? After taking a quick poll on Twitter, it sounded like the best option for me would be to have the non-Mac App Store version look reach inside my app’s sandbox if it existed. To do this, I just had to build some extra code into the method that returns my Application Support path. Here’s the new implementation:

+ (NSString *) seasonalityCoreSupportPath { NSFileManager *fm = [NSFileManager defaultManager]; #ifndef MAC_APP_STORE // Check if ~/Library/Containers/BundleID/Data/Library/Application Support/Seasonality Core exists. NSString *sandboxedAppSupportPath = [NSString pathWithComponents: [NSArray arrayWithObjects:@"~", @"Library", @"Containers", [[NSBundle mainBundle] bundleIdentifier], @"Data", @"Library", @"Application Support", @"Seasonality Core", nil] ]; sandboxedAppSupportPath = [sandboxedAppSupportPath stringByExpandingTildeInPath]; BOOL isDir; if ([fm fileExistsAtPath:sandboxedAppSupportPath isDirectory:&isDir]) { // We found a sandboxed Application Support directory, return it. if (isDir) return sandboxedAppSupportPath; } #endif NSArray *appSupportURLs = [fm URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask]; NSString *appSupportDirectory = nil; if (appSupportURLs.count > 0) { NSURL *firstPath = [appSupportURLs objectAtIndex:0]; appSupportDirectory = [firstPath path]; } return [appSupportDirectory stringByAppendingPathComponent:@"Seasonality Core"]; }

The new code only runs if the MAC_APP_STORE isn’t defined (these are project definitions I have set elsewhere for the different builds). We check to see if there is a sandbox for the app, and if so it will return the sandboxed directory. Otherwise it returns the standard Application Support directory.

This is a pretty complete solution, except that I wanted to make sure the user’s preferences were saved between the two app versions as well. NSUserDefaults won’t know to check for the existence of a sandbox. Daniel Jalkut gracefully offered this solution, which I have since adapted into my own code as follows:

+ (BOOL) gsImportNewerPreferencesForBundle:(NSString *)bundleName fromSandboxContainerID:(NSString *)containerID { BOOL didMigrate = NO; NSArray *libraryFolders = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES); if (libraryFolders.count) { // Get a path to our app's preference file. NSString *prefsFile = [NSString pathWithComponents:[NSArray arrayWithObjects: [libraryFolders objectAtIndex:0], @"Preferences", bundleName, nil ]]; prefsFile = [prefsFile stringByAppendingPathExtension:@"plist"]; // Get a path to the same preference file in the given sandbox container. NSString *containerPrefsFile = [NSString pathWithComponents:[NSArray arrayWithObjects: [libraryFolders objectAtIndex:0], @"Containers", containerID, @"Data", @"Library", @"Preferences", bundleName, nil ]]; containerPrefsFile = [containerPrefsFile stringByAppendingPathExtension:@"plist"]; NSFileManager* fm = [NSFileManager defaultManager]; if ([fm fileExistsAtPath:containerPrefsFile]) { NSDate *prefsModDate = [[fm attributesOfItemAtPath:prefsFile error:nil] objectForKey:NSFileModificationDate]; NSDate *containerModDate = [[fm attributesOfItemAtPath:containerPrefsFile error:nil] objectForKey:NSFileModificationDate]; if ((prefsModDate == nil) || ([prefsModDate compare:containerModDate] == NSOrderedAscending)) { // Copy the file. [fm copyItemAtPath:containerPrefsFile toPath:prefsFile error:nil]; // Reset so the next call to [NSUserDefaults standardUserDefaults] // recreates an object to the new prefs file. [NSUserDefaults resetStandardUserDefaults]; NSLog(@"Found newer preferences in %@ - importing", containerPrefsFile); didMigrate = YES; } } } return didMigrate; }

I call the above preferences migration code directly from main(), so it executes before the any part of the main app might hit NSUserDefaults. Works pretty well thus far.

© 2017 *Coder Blog

Theme by Anders NorenUp ↑