Author: James

Looking for folk to help

I’m currently taking a bit of a break from regular work. I can’t remember the last time I didn’t have a deadline or an imminent sense of expectation – I’ve been digging on the coal face of professional software development almost constantly for 27 years. I’m lucky to be in a position, through hard work over those 27 years and circumstance, that I don’t need to find paid work “tomorrow” and so I’m taking a little time out to recharge, scratch a few itches, complete a couple of side projects, and figure out what I want to do next.

I’m a week in and one of the things that dawned on me is that I’m not really talking to people in the day and I miss helping fellow developers with technical problems and career development and that general chewing of the fat that goes on. Whatever I do next has to include this!

And so I figured a way to perhaps solve that problem and give something back to the community would be to offer some of my time to help others and hopefully everyone wins.

As a starting point I figure offering a regular hour a week to 3 people would be a good place to start. A space to knowledge share, talk through technical issues, career issues, approaches, challenges, look at code – basically whatever helps. I’m likely to favor helping people at the start of their career – just because its so hard when you’re faced with the entire crazy world of software development and the choices you make then reverberate down the rest of your career.

If its of interest then drop me a DM on Twitter with just a short note of how you think I might be able to help and we can set up a Teams or Zoom call and talk it through and see if its a good fit. I’m on UK time. If I end up over subscribed (feels very hubristic to say but I think its important to be clear) I’ll need to filter down to 3 people and that will be based on where I think I can have the most impact.

As some background on me – I’m based in the UK and I taught myself to code in the 80s on the 8-bit micros (a BBC Model B and Master 128) and moved into professional software development I think in 1994. I skipped university – I had a place to do Computer Science but school had left a really nasty taste in my mouth and I managed to find a job through sending in some source code to businesses (on a 3.5″ floppy!). Over the years I’ve done just about every role from writing code to managing large teams, working in product teams, consultancy, corporates, and startups. Making things is my passion and my natural home is small teams with a good deal of end to end autonomy. Tech wise – I started out with BBC Basic and 6502 assembly. Spent most of the 90s doing C and C++ on 68000 based embedded systems and PCs. Then the 2000s has been mostly .NET and web technologies (JavaScript, TypeScript, React, CSS etc.) – more recently I’ve got heavily into my functional programming with F# but still use C#. I’ve been all over Azure for the last 10 years and more recently have been dabbling with AWS and Digital Ocean.

Outside of tech – I’m (to say the least) a keen cyclist love to cycle up mountains. Sadly I live somewhere pan flat so COVID has been somewhat limiting this last year.

Pholly

While working on a fun little F# side project over the Christmas break I had the need for a couple of resilience policies. I had a Google but couldn’t really find much in the space and what I was really looking for was an elegant way to use the battle hardened Polly with my F# code. It wasn’t critical so I parked it and moved on.

The itch kept, errr, itching and so I came back to it this last week looking to build an expressive F# feeling wrapper for Polly and the result of that is Pholly (hat tip to Scott Wlaschin for the name).

So far it includes support for the three reactive Polly policies: retry, circuit breaker and fallback and they can be used in both asynchronous and synchronous situations. The async makes use of tasks via the Ply task builder – this seemingly being the direction of travel and, as Isaac Abraham pointed out, with F# async we’d be wrapping one form of async computation with another.

The library is available on NuGet, GitHub, and documented in a Dotnet Interactive Notebook however as an example constructing and using a retry policy looks like this:

let retryPolicy =
  Policy.retryAsync [
    retry (upto 10<times>)
    withIntervalsOf [50<ms> ; 150<ms> ; 500<ms>]
    beforeEachRetry (fun _ t _ -> printf "onRetry %d\n" t ; ())
  ]    
let! resultOne = asyncWorkload |> retryPolicy

Future changes at this point include a more “word based” approach to policy composition as their are strong around current custom operators as used currently and addition of the pro-active policies.

Its an early version number on NuGet but I’m actively using it and I’ll bump it up to a 1.0.0 in fairly short order – any problems please let me know in a GitHub issue or on Twitter.

2020 Review

I think the most generous thing you could say about 2020 is that it was strange – nevertheless there have been some highs and lows and so, in true narcissistic fashion, are some of my personal high lights and low lights as a middle aged grumpy bastard working in tech.

F#

A real highlight for me. Over 2019 I’d started to become ever more frustrated by C# with this exciting numbered list of issues bothering me the most:

  1. I increasingly felt I was “fighting” the language – its object oriented roots support patterns we’ve mostly decided are unhelpful to us now and while C# has evolved its still held back by those roots.
  2. Ever increasing complexity – as its evolved lots of complexity has been added and it shows no sign of slowing down.
  3. As a combination of (1) and (2) there is no longer an obvious path of least resistance.
  4. When I started to pick up F# some of the things I felt were helpful (immutability being a standout) were not baked into C#. They’re still not common.

In any case. I jumped ship to F# as I recognized my approach was increasingly functional with a dash of object orientation – which really is F#s bag. Their was definitely a steep learning curve to “do things well” (I mean how the actual fuck do you do a loop without being able to change a variable) but the pay off has been massive.

Their is also some fantastic framework support – the SAFE stack comes to mind.

In any case – at the end of it all I’ve never felt as productive as I do with F# – it was well worth the effort to learn. And there are some fantastic folk in the F# community – helpful, friendly, thoughtful and generous with their time.

Getting to talk at fsharpConf 2020 was bloody amazing too! A real genuine highlight of 2020 for me.

Receiving an MVP Award

That was super cool. It was nice to be recognized and I was super appreciative of the award and the nomination.

I’m not sure if I’ll be renewed – my community contributions have fallen off (change of role, bit burned out with everything going on) and its fair to say I’m about a million miles away from towing any kind of Microsoft line and have found myself quiet critical on a number of occasions this year (I hope they appreciate critical friends….!).

OSS in .NET land

I’ve bailed on this. To me at this point it seems like something of a lost cause. A mugs game as I’ve written before. Yes Microsoft are having another trip around the “how could we improve this” but the published pieces I’ve seen aren’t really encouraging.

I’m not going to blather on about it all here. Aaron Stannard has published many many thoughtful articles on the topic but my takeaway is: you’re better off working in another ecosystem or simply accepting the ecosystem for what it is, leveraging it as it stands, and building a product and business out of it. OSS may form part of your strategy (likely adoption and PR), it may not.

AWS

I became curious about AWS while poking around their support for ARM processors and discovering a significant economic advantage was to be had. I’ve migrated my bike performance website over to it and learned a lot through that. Ironically they seem to be making better use of .NET than Microsoft and Lambda is fantastic (having a full suite of F# blueprints puts Azure Functions to shame frankly).

If I was to compare AWS with Azure I would say that AWS feels more low level and like it is built for developers by developers with a more consistent foundation. You can get going more easily with Azure “in just 5 minutes” but once you get past that facade AWS just feels more “put together” to me. I can’t imagine working with AWS without a robust infrastructure as code solution (I’ve been using Pulumi).

If I was to start a new project today as a .NET developer what would I choose? AWS.

(and in fact I have been doing that this last couple of weeks)

A full year as CTO

If I’d known we were going to have a pandemic I’m not sure I’d have moved into a role that took me so far out of my comfort zone. Its had highs and lows. I still have the itch to make things (myself) and COVID + CTOing has left me too exhausted to scratch it in my free time which has been frustrating and led to quite a few started and unfinished projects. I’m trying not to beat myself up too much about that.

Personal stuff

Like many my mental health has definitely taken a battering this year – I’ve not been effected directly by COVID in terms of my health or my job (beyond operational issues) but I’ve seen people who are and its “always there” like a nasty hum in the background – combined with the role change it really gave me a pounding. I’d also finally in my 40s figured out ways to compensate for the things I don’t have in my life by putting other things in their place and they’ve ben cut off by the pandemic.

See the source image
End of the year James

I crawled into the Christmas break with quite bad insomnia, what I can only describe as “micro-panics” each night when I went to bed, and an utter absence of energy and enthusiasm.

It took about 12 days of my Christmas break to start feeling back to even vaguely normal again. I’m nervous as to how I’ll get on in the first quarter of 2021 but will push on.

Looking ahead

I’ve been thinking about what I’m good at and what interests me. Really its doing early stage product development on small budgets / tight resource constraints with tiny teams / solopreneur land. I love it and I’ve got quite practiced at optimizing the development side of it. I’m thinking maybe their is some writing and perhaps even business opportunity around that.

I’ve also got a truck load of product ideas. I always do….

Integrating Pulumi Stack Output with GitHub Actions

As part of migrating Performance for Cyclists to AWS I’ve been exploring the use of Pulumi to manage the infrastructure running through GitHub Actions when I commit code (targetting dev) or to live (when I create a release). To do this I’m using the Pulumi GitHub Action available in the marketplace.

This has been fairly straightforward if a little verbose compared to Farmer (which I use to do the same with Azure) – with one exception: using a Pulumi Stack Output in a subsequent GitHub Action step. For example passing the URL of a provisioned application load balancer on to an acceptance test suite or the endpoint for a database that I want to run a migration on.

I scratched my head for a while before stumbling on the secret to doing this in a GitHub Issue. It took a little bit of tweaking to get it actually working due to the way the Pulumi Action wraps the output command – it was a bit fiddly getting the escape sequences right. In any case here are the steps.

Firstly after your pulumi up step add a step that looks like this:

- name: Extract stack output
  uses: docker://pulumi/actions
  id: pulumiStackOutput
  with:
    args: stack output -j | jq --raw-output "'"to_entries | map(\"::set-output name=\" + .key+\"::\" + (.value | tostring)+\"^\") | .[]"'" | xargs -d \"^\" echo 
  env:
    PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
    PULUMI_ROOT: aws
    PULUMI_CI: stack

Its important your step has an ID so that you can reference it subsequently. Now lets assume one of your output variables is ApiUrl then you would consume it like the below (just showing it in a dotnet run type command):

- name: Test endpoint
  run: dotnet run -- ${{ steps.pulumiStackOutput.outputs.ApiUrl }}

This seems a common task in a real world CI / CD pipeline to me so its surprising that its not supported directly in the Action. As best I can tell (and I looked in the source) it doesn’t seem to be though Pull Requests have been created for it back in April. Hopefully they’ll add this capability soon as it feels very sticking plaster and over-complex without. Really I just want to be able to add an option like PULUMI_EXPOSE_OUTPUTS in the example below:

- name: Infrastructure up
  uses: docker://pulumi/actions
  with:
    args: up --yes
  env:
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    PULUMI_ACCESS_TOKEN: ${{ secrets.PULUMI_ACCESS_TOKEN }}
    PULUMI_CI: up
    PULUMI_ROOT: aws
    PULUMI_EXPOSE_OUTPUTS: true

Migrating www.forcyclistsbycyclists.com to AWS from Azure (Part 1)

If you follow me on Twitter you might have seen that as a side project I run a cycling performance analytics website called Performance For Cyclists – this is currently running on Azure.

Its built in F# and deploys to Azure largely like this (its moved on a little since I drew this but not massively):

It runs fine but if you’ve been following me recently you’ll know I’ve been looking at AWS and am becoming somewhat concerned that Microsoft are falling behind in a couple of key areas:

  • Support for .NET – AWS seem to always be a step ahead in terms of .NET running in serverless environments with official support for the latest runtimes rolling out quickly and the ability to deploy custom runtimes if you need. Cold starts are much better and they have the capability to run an ASP.Net Core application serverlessly with much less fuss.

    I can also, already, run .NET on ARM on AWS which leads me to my second point (its almost as if I planned this)…
  • Lower compute costs – my recent tests demonstrated that I could achieve a 20% to 40% saving depending on the workload by making use of ARM on AWS. It seems clear that AWS are going to broaden out ARM yet further and I can imagine them using that to put some distance between Azure and AWS pricing.

    I’ve poked around this as best I can with the channels available to me but can’t get any engagement so my current assumption is Microsoft aren’t listening (to me or more broadly), know but have no response, or know but aren’t yet ready to reveal a response.

(just want to be clear about something – I don’t have an intrinsic interest in ARM, its the outcomes and any coupled economic opportunities that I am interested in)

I’m also just plain and simpe curious. I’ve dabbled with AWS, mostly when clients were using it when I freelanced, but never really gone at it with anything of significant size.

I’m going to have to figure my way through things a bit, and doubtless iterate, but at the moment I’m figuring its going to end up looking something like this:

Leaving Azure Maps their isn’t a mistake – I’m not sure what service on AWS offers the functionality I need, happy to here suggestions on Twitter!

I may go through this process and decide I’m going to stick with Azure but worst case is that I learn something! Either way I’ll blog about what I learn. I’ve already got the API up and running in ECS backed by Fargate and being built and deployed through GitHub Actions and so I’ll write about that in my next post.

Compute “Bang for Buck” on Azure and AWS – 20% to 40% advantage on AWS

As I normally post from a developer perspective I thought it might be worth starting off with some additional context for this post. If you follow me on Twitter you might know that about 14 months ago I moved into a CTO role at a rapidly growing business – we’re making ever increasing use of the cloud both by migrating workloads and the introduction of new workloads. Operational cost is a significant factor in my budget. To me the cloud can be summarised as “cloud = economics + capabilities” and so if I have a similar set of capabilities (or at least capabilities that map to my needs) then reduction in compute costs has the potential to drive the choice of vendor and unlock budget I can use to grow faster.

In the last few posts I’ve been exploring the performance of ARM processors in the cloud but ultimately what matters to me is not a processor architecture but the economics it brings – how much am I paying for a given level of performance and set of characteristics.

It struck me there were some interesting differences across ARM, x86, Azure and AWS and I’ve expanded my testing and attempted here to present these findings in (hopefully) useful terms.

All tests have been run on CentOS Linux (or the AWS derivative) using the .NET 5 runtime with Apache acting as a reverse proxy to Kestrel. I’ve followed the same setup process on every VM and then run performance tests directly against their public IP using loader.io all within the USA.

I’ve run two workloads:

  1. Generate a Mandelbrot – this is computationally heavy with no asynchronous yield points.
  2. A test that simulates handing off asynchronously to remote resources. I’ve included a small degree of randomness in this.

At the bottom of the post is a table containing the full set of tests I’ve run on the many different VM types available. I’m going to focus on some of the more interesting scenarios here.

Computational Workload

2 Core Tests

For these tests I picked what on AWS is a mid range machine and on Azure the entry level D series machine:

AWS (ARM): t4g.large – a 2 core VM with 8GiB of RAM and costing $0.06720 per hour
AWS (x86): t3.large – a 2 core VM with 8GiB of RAM and costing $0.08320 per hour
Azure (x86): D2s v4 – a 2 core VM with 8GiB of RAM and costing $0.11100 per hour

On these machines I then ran the workloads with different numbers of clients per seconds and measured their response times and the failure rate (failure being categorised as a response of > 10 seconds):

Both Intel VMs generated too many errors at the 25 client per second rate and the load tester aborted.

Its clear from these results that the ARM VM running on AWS has a significant bang for buck advantage – its more performant than the Intel machines and is 20% cheaper than the AWS Intel machine and 40% cheaper than the Azure machine.

Interestingly the Intel machine on AWS lags behind the Intel machine on Azure particularly when stressed. It is however around 20% cheaper and it feels as if performance between the Intel machines is largely on the same economic path (the AWS machine is slightly ahead if you normalise the numbers).

4 Core Tests

I wanted to understand what a greater number of cores would do for performance – in theory it should let me scale past the 20 client per second level of the smaller instances. Having concluded that ARM represented the best value for money for this workload on AWS I didn’t do an x86 test on AWS. I used:

AWS: t4g.xlarge (ARM) – a 4 core VM with 16GiB of RAM and costing $0.13440 per hour
Azure: D4s_v4 – a 4 core VM with 16GiB of RAM and costing $0.22200 per hour

I then ran the workloads with different numbers of clients per seconds and measured their response times and the failure rate (failure being categorised as a response of > 10 seconds):

The Azure instance failed the 55 client per second rate – it had so many responses above 10 seconds in duration that the load test tool aborted the test.

Its clear from these graphs that the ARM VM running on AWS outperforms Azure both in terms of response time and massively in terms of bang for buck – its nearly half the price of the corresponding Azure VM.

Starter Workloads

One of the nice things about AWS and Azure is they offer very cheap VMs. The Azure VMs are burstable (and there is some complexity here with banked credits) which makes them hard to measure but as we saw in a previous post the ARM machines perform very well at this level.

The three machines used are:

AWS (ARM): t4g.micro, 2 core, 1GiB of RAM costing $0.00840 per hour
Azure (x86): B1S, 1 core, 1GiB of RAM costing $0.00690 per hour
AWS (x86): t3.micro, 2 core, 1 GiB of RAM costing $0.00840 per hour

Its an easy victory for ARM on AWS here – its performant, cheap and predictable. The B1S instance on Azure couldn’t handle 15 or 20 clients per second at all but may be worth consideration if its bursting system works for you.

Simulated Async Workload

2 Core Tests

For these tests I used the same configurations as in the computational workload.

Their is less to separate the processors and vendors with a less computationally intensive workload. Interestingly the AWS machines have a less stable response time with more > 10 second response times but, in the case of the ARM chip, it does this while holding a lower average response time while under load.

Its worth noting that the ARM VM is doing this at 40% of the cost of the Azure VM and so I would argue again represents the best bang for buck. The AWS x86 VM is 20% cheaper than the Azure equivelant – if you can live with the extra “chop” that may still be worth it or you can use that saving to purchase a bigger tier unit.

4 Core Tests

For these tests I used the same virtual machines as for the computational workload:

There is little to separate the two VMs until they come under heavy load at which point we see mixed results – I would argue the ARM VM suffers more as it becomes much more spiky with no consistent benefit in average response time.

However in terms of bang for buck – this ARM VM is nearly half the price of the Azure VM. There’s no contest. I could put two of these behind a load balancer for nearly the same cost.

Starter Workloads

For these tests I used the same virtual machines as for the computational workload:

Its a pretty even game here until we hit the 100 client per second range at which point the AWS VMs begin to outperform the Azure VM though at the 200 client per second range at the expense of more long response times.

Conclusions

Given the results, at least with these workloads, its hard not to conclude that AWS currently offers significantly greater bang for buck than Azure for compute. Particularly with their use of ARM processors AWS seem to have taken a big leap ahead in terms of value for money for which, at the moment, Azure doesn’t look to have any response.

Perhaps tailoring Azure VMs to your specific workloads may get you more mileage.

I’ve tried to measure raw compute here in the simplest way I can – I’d stress that if you use more managed services you may see a different story (though ultimately its all running on the same infrastructure so my suspicion is not). And as always, particularly if you’re considering a switch of vendor, I’d recommend running and measuring representative workloads.

Full Results

TestVendorInstanceClients per secondMinMaxAverageSuccessful ResponsesTimeouts> 10 secondsPrice per hour
MandelbrotAzureA2_V2 (x64)29179279346000.0%$0.10600
MandelbrotAzureA2_V2 (x64)51263664939755600.0%$0.10600
MandelbrotAzureA2_V2 (x64)101205102037985342138.2%$0.10600
MandelbrotAzureA2_V2 (x64)15ERROR RATE TOO HIGH#DIV/0!$0.10600
MandelbrotAzureA2_V2 (x64)20ERROR RATE TOO HIGH#DIV/0!$0.10600
AsyncAzureA2_V2 (x64)2017334325260000.0%$0.10600
AsyncAzureA2_V2 (x64)50196504274149800.0%$0.10600
AsyncAzureA2_V2 (x64)10023942402484179400.0%$0.10600
AsyncAzureA2_V2 (x64)20042389295475172500.0%$0.10600
MandelbrotAzureB1S (x86)2670255111715700.0%$0.00690
MandelbrotAzureB1S (x86)51612552132527200.0%$0.00690
MandelbrotAzureB1S (x86)1012591000171157222.7%$0.00690
MandelbrotAzureB1S (x86)15ERROR RATE TOO HIGH$0.00690
AsyncAzureB1S (x64)2020638326858000.0%$0.00690
MandelbrotAzureB1S (x86)20ERROR RATE TOO HIGH$0.00690
AsyncAzureB1S (x64)50209436278149800.0%$0.00690
AsyncAzureB1S (x64)10029231511892225200.0%$0.00690
AsyncAzureB1S (x64)20048277084474213600.0%$0.00690
MandelbrotAzureD1 v2 (x64)27488287876000.0%$0.08780
MandelbrotAzureD1 v2 (x64)52858424236467000.0%$0.08780
MandelbrotAzureD1 v2 (x64)1011921000175235758.1%$0.08780
MandelbrotAzureD1 v2 (x64)15ERROR RATE TOO HIGH$0.08780
MandelbrotAzureD1 v2 (x64)20ERROR RATE TOO HIGH$0.08780
AsyncAzureD1 v2 (x64)20$0.08780
AsyncAzureD1 v2 (x64)50168407244149900.0%$0.08780
AsyncAzureD1 v2 (x64)10024133981986215600.0%$0.08780
AsyncAzureD1 v2 (x64)20040791714927195100.0%$0.08780
MandelbrotAzureD2as_v425596045666000.0%$0.11100
MandelbrotAzureD2as_v455872606159613300.0%$0.11100
MandelbrotAzureD2as_v41013055920354113400.0%$0.11100
MandelbrotAzureD2as_v41513589607559612600.0%$0.11100
AsyncAzureD2as_v42020030523960000.0%$0.11100
MandelbrotAzureD2as_v4206381237974351043324.1%$0.11100
MandelbrotAzureD2as_v4251459102938850587054.7%$0.11100
AsyncAzureD2as_v450200312238149800.0%$0.11100
AsyncAzureD2as_v4100202347247300000.0%$0.11100
AsyncAzureD2as_v420029541292053427600.0%$0.11100
AsyncAzureD2as_v43003291126931904334230.5%$0.11100
AsyncAzureD2as_v440033817305397842472054.6%$0.11100
MandelbrotAWSt2.micro (x86)2675114010105800.0%$0.01160
MandelbrotAWSt2.micro (x86)5651532433327200.0%$0.01160
MandelbrotAWSt2.micro (x86)10186710193699956812.5%$0.01160
MandelbrotAWSt2.micro (x86)151445102039458324457.9%$0.01160
AsyncAWSt2.micro (x64)2024241229860000.0%$0.01160
MandelbrotAWSt2.micro (x86)201486102068895114078.4%$0.01160
AsyncAWSt2.micro (x64)50241545312149700.0%$0.01160
AsyncAWSt2.micro (x64)10024498292260198900.0%$0.01160
AsyncAWSt2.micro (x64)200347173753858211825210.6%$0.01160
MandelbrotAWSt3.micro (x86)27018857446000.0%$0.01040
MandelbrotAWSt3.micro (x86)58783313206910800.0%$0.01040
MandelbrotAWSt3.micro (x86)108558037449810300.0%$0.01040
MandelbrotAWSt3.micro (x86)159731020269308499.7%$0.01040
AsyncAWSt3.micro (x64)2023340227960000.0%$0.01160
MandelbrotAWSt3.micro (x86)201030102158495743532.1%$0.01040
AsyncAWSt3.micro (x64)502354912407149800.0%$0.01160
AsyncAWSt3.micro (x64)100235545292299400.0%$0.01160
AsyncAWSt3.micro (x64)20023417376259830892607.8%$0.01160
MandelbrotAWSt4g.large (ARM)26327796546000.0%$0.06720
MandelbrotAWSt4g.large (ARM)56982436175313700.0%$0.06720
MandelbrotAWSt4g.large (ARM)1019366284368213700.0%$0.06720
MandelbrotAWSt4g.large (ARM)1521209927562413300.0%$0.06720
MandelbrotAWSt4g.large (ARM)208651020774721003123.7%$0.06720
MandelbrotAWSt4g.large (ARM)25757102078432568058.8%$0.06720
AsyncAWSt4g.large (ARM)2023439828059900.0%$0.06720
AsyncAWSt4g.large (ARM)50229395275149800.0%$0.06720
AsyncAWSt4g.large (ARM)100236426287299200.0%$0.06720
AsyncAWSt4g.large (ARM)20031617359208040262606.1%$0.06720
AsyncAWSt4g.large (ARM)300241173813322306063917.3%$0.06720
AsyncAWSt4g.large (ARM)4003491312733464038108821.2%$0.06720
MandelbrotAWSt4g.micro (ARM)26187516386000.0%$0.00840
MandelbrotAWSt4g.micro (ARM)57652794170913200.0%$0.00840
MandelbrotAWSt4g.micro (ARM)107616958388213000.0%$0.00840
MandelbrotAWSt4g.micro (ARM)1575910203570412710.8%$0.00840
AsyncAWSt4g.micro (ARM)2023637127560000.0%$0.00840
MandelbrotAWSt4g.micro (ARM)208021020774591191410.5%$0.00840
AsyncAWSt4g.micro (ARM)502224178373149800.0%$0.00840
AsyncAWSt4g.micro (ARM)100231414286299400.0%$0.00840
AsyncAWSt4g.micro (ARM)20031017388202839952004.8%$0.00840
AsyncAzureD4s_v42016723920060000.0%$0.22200
AsyncAzureD4s_v450165242197149900.0%$0.22200
AsyncAzureD4s_v4100153243198300000.0%$0.22200
AsyncAzureD4s_v4200165270204600000.0%$0.22200
AsyncAzureD4s_v430020899621395790000.0%$0.22200
AsyncAzureD4s_v440021116283204976951141.5%$0.22200
MandelbrotAzureD4s_v423043343136000.0%$0.22200
MandelbrotAzureD4s_v4541567550015000.0%$0.22200
MandelbrotAzureD4s_v41048834501670238800.0%$0.22200
MandelbrotAzureD4s_v4154864371301225600.0%$0.22200
MandelbrotAzureD4s_v4207276572402723900.0%$0.22200
MandelbrotAzureD4s_v42514538024512723500.0%$0.22200
MandelbrotAzureD4s_v4308869282598823800.0%$0.22200
MandelbrotAzureD4s_v435613100056850196198.8%$0.22200
MandelbrotAzureD4s_v4401817133527905215146.1%$0.22200
MandelbrotAzureD4s_v44524121020786392044116.7%$0.22200
MandelbrotAzureD4s_v4507471020789538015866.4%$0.22200
MandelbrotAzureD4s_v455ERROR RATE TOO HIGH
MandelbrotAzureD2s v424594824696000.0%$0.11100
MandelbrotAzureD2s v458833449176412300.0%$0.11100
MandelbrotAzureD2s v4104806747405312300.0%$0.11100
MandelbrotAzureD2s v41548310202628611810.8%$0.11100
MandelbrotAzureD2s v420506102067636862824.6%$0.11100
MandelbrotAzureD2s v425ERROR RATE TOO HIGH$0.11100
AsyncAzureD2s v42016827020658000.0%$0.11100
AsyncAzureD2s v450164266205149900.0%$0.11100
AsyncAzureD2s v4100167310217300000.0%$0.11100
AsyncAzureD2s v420025938182233399000.0%$0.11100
AsyncAzureD2s v43002491560335923808120.3%$0.11100
AsyncAzureD2s v440033016811434139142034.9%$0.11100
MandelbrotAWSt3.large (x86)27118787536000.0%$0.08320
MandelbrotAWSt3.large (x86)57583150202411300.0%$0.08320
MandelbrotAWSt3.large (x86)1010237656439311500.0%$0.08320
MandelbrotAWSt3.large (x86)15234010202661510443.7%$0.08320
MandelbrotAWSt3.large (x86)202453104068479475453.5%$0.08320
MandelbrotAWSt3.large (x86)25ERROR RATE TOO HIGH$0.08320
AsyncAWSt3.large (x86)2023438028060000.0%$0.08320
AsyncAWSt3.large (x86)50230386276144800.0%$0.08320
AsyncAWSt3.large (x86)100235424287299000.0%$0.08320
AsyncAWSt3.large (x86)20023717373280830262708.2%$0.08320
AsyncAWSt3.large (x86)300230173823358312760316.2%$0.08320
AsyncAWSt3.large (x86)4005511312734513664108822.9%$0.08320
AsyncAWSt4g.xlarge (ARM)2023138427459900.0%$0.13440
AsyncAWSt4g.xlarge (ARM)50221380273149800.0%$0.13440
AsyncAWSt4g.xlarge (ARM)100221382273299500.0%$0.13440
AsyncAWSt4g.xlarge (ARM)200222395276600000.0%$0.13440
AsyncAWSt4g.xlarge (ARM)300266102097068699560.6%$0.13440
AsyncAWSt4g.xlarge (ARM)4002581110619537793108812.3%$0.13440
MandelbrotAWSt4g.xlarge (ARM)26337796466000.0%$0.13440
MandelbrotAWSt4g.xlarge (ARM)5581111573715000.0%$0.13440
MandelbrotAWSt4g.xlarge (ARM)106134203154026400.0%$0.13440
MandelbrotAWSt4g.xlarge (ARM)1510796666270826400.0%$0.13440
MandelbrotAWSt4g.xlarge (ARM)2011786820372327200.0%$0.13440
MandelbrotAWSt4g.xlarge (ARM)257518171442526400.0%$0.13440
MandelbrotAWSt4g.xlarge (ARM)3067710231555526562.2%$0.13440
MandelbrotAWSt4g.xlarge (ARM)35687102456356239259.5%$0.13440
MandelbrotAWSt4g.xlarge (ARM)40895103347353229228.8%$0.13440
MandelbrotAWSt4g.xlarge (ARM)4510411020780441995321.0%$0.13440
MandelbrotAWSt4g.xlarge (ARM)5012361020686241737630.5%$0.13440
MandelbrotAWSt4g.xlarge (ARM)55119310206917910516160.5%$0.13440

.NET 5 – ARM vs x64 in the Cloud Part 2 – Azure

Having conducted my ARM and x64 tests on AWS yesterday I was curious to see how Azure would fair – it doesn’t support ARM but ultimately that’s a mechanism for delivering value (performance and price) and not an end in and of itself. And so this evening I set about replicating the tests on Azure.

In the end I’ve massively limited my scope to two instance sizes:

  1. A2 – this has 2 CPUs and 4Gb of RAM (much more RAM than yesterdays) and costs $0.120 per hour
  2. B1S – a burstable VM that has 1 CPUand 1Gb RAM (so most similar to yesterdays t2.micro) and costs $0.0124 per hour

Note – I’ve begun to conduct tests on D series too, preliminary findings is that the D1 is similar to the A2 in performance characteristics.

I was struggling to find Azure VMs with the same pricing as AWS and so had to start with a burstable VM to get something in the same kind of ballpark. Not ideal but they are the chips you are dealt on Azure! I started with the B1S which was still more expensive than the ARM VM. I created the VM, installed software, and ran the tests – the machine comes with 30 credits for bursting. However after running tests several times it was still performing consistently so these were either exhausted quickly, made little difference, or were used consistently.

I moved to the A2_V2 because, frankly, the performance was dreadful on my early tests with the B1S and I also wanted something that wouldn’t burst. I was also trying to match the spec of the AWS machines – 2 cores and 1Gb of RAM. I’ll attempt the same tests with a D series when I can.

Test setup was the same and all tests are run on VMs accessed directly on their public IP using Apache as a reverse proxy to Kestrel and our .NET application.

I’ve left the t2.micro instance out of this analysis

Mandelbrot

With 2 clients per test we see the following response times:

We can see that the two Azure instances are already off to a bad start on this computationally heavy test.

At 10 clients per second we continue to see this reflected:

However at this point the two Azure instances begin to experience timeout failures (the threshold being set at 10 seconds in the load tester):

The A2_V2 instance is faring particularly badly particularly given it is 10x the cost of the AWS instances.

Unfortunately their is no meaningful compaison I can make under higher load as both Azure instances collapse when I push to 15 clients per second. For complete sake here are the results on AWS at 20 clients per second (average response and total requests):

Simulated Async Workload

With our simulated async workload Azure fares better at low scale. Here are the results at 20 requests per second:

As we push the scale up things get interesting with different patterns across the two vendors. Here are the average response times at 200 clients per second:

At first glance AWS looks to be running away with things however both the t4g.micro and t3.micro suffer from performance degradation at the extremes – the max response time is 17 seconds for both while for the Azure instances it is around 9 seconds.

You can see this reflected in the success and total counts where the AWS instances see a number of timeout failures (> 10 seconds) while the Azure instances stay more consistent:

However the AWS instances have completed many more requests overall. I’ve not done a percentile breakdown (see comments yesterday) but it seems likely that at the edges AWS is fraying and degrading more severely than Azure leading to this pattern.

Conclusions

The different VMs clearly have different strengths and weaknesses however in the computational test the Azure results are disappointing – the VMs are more expensive yet, at best, offer performance with different characteristics (more consistent when pushed but lower average performance – pick your poison) and at worst offer much lower performance and far less value for money. They seem to struggle with computational load and nosedive rapdily when pushed in that scenario.

Full Results

TestVendorInstanceClients per secondMinMaxAverageSuccessful ResponsesTimeouts
MandelbrotAWSt4g.micro (ARM)2618751638600
MandelbrotAWSt4g.micro (ARM)5765279417091320
MandelbrotAWSt4g.micro (ARM)10761695838821300
MandelbrotAWSt4g.micro (ARM)157591020357041271
MandelbrotAWSt4g.micro (ARM)2080210207745911914
MandelbrotAWSt3.micro (x64)2701885744600
MandelbrotAWSt3.micro (x64)5878331320691080
MandelbrotAWSt3.micro (x64)10855803744981030
MandelbrotAWSt3.micro (x64)15973102026930849
MandelbrotAWSt3.micro (x64)2010301021584957435
MandelbrotAWSt2.micro (x64)267511401010580
MandelbrotAWSt2.micro (x64)565153243332720
MandelbrotAWSt2.micro (x64)101867101936999568
MandelbrotAWSt2.micro (x64)1514451020394583244
MandelbrotAWSt2.micro (x64)2014861020688951140
MandelbrotAzureA2_V2 (x64)2917927934600
MandelbrotAzureA2_V2 (x64)5126366493975560
MandelbrotAzureA2_V2 (x64)1012051020379853421
MandelbrotAzureA2_V2 (x64)15ERROR RATE TOO HIGH
MandelbrotAzureA2_V2 (x64)20ERROR RATE TOO HIGH
MandelbrotAzureB1S (x64)267025511171570
MandelbrotAzureB1S (x64)5161255213252720
MandelbrotAzureB1S (x64)101259100017115722
MandelbrotAzureB1S (x64)15ERROR RATE TOO HIGH
MandelbrotAzureB1S (x64)20ERROR RATE TOO HIGH
AsyncAWSt4g.micro (ARM)202363712756000
AsyncAWSt4g.micro (ARM)50222417837314980
AsyncAWSt4g.micro (ARM)10023141428629940
AsyncAWSt4g.micro (ARM)2003101738820283995200
AsyncAWSt3.micro (x64)202334022796000
AsyncAWSt3.micro (x64)50235491240714980
AsyncAWSt3.micro (x64)10023554529229940
AsyncAWSt3.micro (x64)2002341737625983089260
AsyncAWSt2.micro (x64)202424122986000
AsyncAWSt2.micro (x64)5024154531214970
AsyncAWSt2.micro (x64)1002449829226019890
AsyncAWSt2.micro (x64)2003471737538582118252
AsyncAzureA2_V2 (x64)201733432526000
AsyncAzureA2_V2 (x64)5019650427414980
AsyncAzureA2_V2 (x64)1002394240248417940
AsyncAzureA2_V2 (x64)2004238929547517250
AsyncAzureB1S (x64)202063832685800
AsyncAzureB1S (x64)5020943627814980
AsyncAzureB1S (x64)1002923151189222520
AsyncAzureB1S (x64)2004827708447421360

.NET 5 – ARM vs x64 in the Cloud

With Microsoft and Apple both now beginning to use ARM chips in laptops, what was traditionally the domain of x86/x64 architecture, I found myself curious as to the ramifications of this move – particularly by Apple who are transitioning their entire lineup to ARM over the next 2 years.

While musing on the pain points of this I found myself wandering if Azure supported ARM processors, they don’t, and got pointed to AWS who do. @thebeebs (an AWS developer advocate) mentioned that some customers had seen significant cost reductions by moving some workloads over to ARM and so I, inevitably, found myself curious as to how typical .NET workloads might run in comparison to x64 and set about some tests.

The Tests

I quickly rustled up a simple API containing two invocable workloads:

  1. A computation heavy workload – I’m rendering a Mandelbrot and returning it as an image. This involves floating point maths.
  2. A simulated await workload – often with APIs we hand off to some other system (e.g. a database) and then do a small amount of computation. I’ve simulated this with Task.Delay and a (very small) random factor to simulate the slight variations you will get with any network / remote service request and then around this I compute two tiny Mandelbrots and return a couple of numbers. It would be nice to come back at some point and use a more structured approach for the simulated remote latency.

I’ve written this in F# (its not particularly “functional”) using Giraffe on top of ASP.Net Core just because that’s my go to language these days. Its running under the .NET 5 runtime.

The code for this is here. Its not particularly elegant and I simply converted some old JavaScript code of mine into F# for the Mandelbrot. It does a job.

The Setup

Within AWS I created three EC2 Linux instances:

  1. t4g.micro – ARM based, 2 vCPU, 1Gb memory, $0.0084 per hour
  2. t3.micro – x64 based, 2 vCPU, 1Gb memory, $0.0104 per hour
  3. t2.micro – x64 based, 1 vCPU, 1Gb memory, $0.0116 per hour

Its worth noting that my ARM instance is costing me 20% less than the t3.micro.

I’ve deliberately chosen very small instances in order to make it easier to stress them without having to sell a kidney to fund the load testing. We should be able to stress these instances quite quickly.

I then SSHed into each box and installed .NET 5 from the appropriate binaries and setup Apache as a reverse proxy. On the ARM machine I also had to install GCC and compile a version of libicui18n for .NET to work.

Next I used git clone to bring down the source and ran dotnet restore followed by dotnet run. At this point I had the same code working on each of my EC2 instances. Easy to verify as the root of the site shows a Mandelbrot:

This was all pretty easy to set up. You can also do it using a Cloud Formation sample that I was pointed at (again by @thebeebs).

I still think its worth remarking how much .NET has changed in the last few years – I’ve not touched Windows here and have the same source running on two different CPU architectures with no real effort on my part. Yes its “get through the door” stakes these days but it was hard to imagine this a few years back.

Benchmarks

My tests were fairly simple – I’ve used loader.io to maintain a steady state of a given number of clients per second and gathered up the response times and total execution counts along with the number of timeouts. I had the timeout threshold set at 10 seconds.

Time allowing I will come back to this and run some percentile analysis – loader doesn’t support this and so I would need to do some additional work.

I’ve run the test several times and averaged the results – though they were all in the same ballpark.

Mandelbrot

Firstly as a baseline lets look at things running with just two clients per second:

With little going on we can see that the ARM instance already has a slight advantage – its consistently (min, max and average) around 100ms faster than the closest x64 based instance.

Unsurprisingly if we push things a little harder to 5 clients per second this becomes magnified:

We’re getting no errors or timeouts at this point and you can see the total throughput over the 30 second run below:

The ARM instance has completed around 20% more requests than the nearest x64 instance, with a 18% improvement in average response time and at 80% of the cost.

And if we push this out to 20 clients per second (my largest scale test) the ARM instance looks better again:

Its worth noting that at this point all three instances are generating timeouts in our load test suite but again the ARM instance wins out here – we get fewer timeouts and get through more overall requests:

You can see from this that our ARM instance is performing much better under this level of load. We can say that:

  • Its successfully completed 60% more requests than the nearest x64 instance
  • It has a roughly 12% improvement on average response time
  • And it is doing this at 80% of the cost of the x64 instance

With our Mandelbrot test its clear that the ARM instance has a consistent advantage both in performance and cost.

Simulated Async Workload

Starting again with a low scale test (in this case 50 clients per second – this test spends significant time awaiting) in this case we can see that our t2 x64 instance had an advantage of around 40ms:

However if we move up to 100 clients per second we can see the t2 instance essentially collapse while out t4g ARM instance and t3 x64 instance are essentially level pegging (286ms and 292ms) respectively:

We get no timeouts at this point and our ARM and x64 instance level peg again on total requests:

However if we push on to a higher scale test (200 clients per second) we can see the ARM instance begin to pull ahead:

Conclusions

Going into this I really didn’t know what to expect but these fairly simple tests suggest their is an economic advantage to running under ARM in the cloud. At worst you will see comparable performance at a lower price point but for some workloads you may see a significant performance gain – again at a lower price point.

20% performance gain at 80% the price is most certainly not to be sniffed at and for large workloads could quickly offset the cost of moving infrastructure to ARM.

Presumably the price savings are due to the power efficiency of the ARM chips. However what is hard to tell is how much of the pricing is “early adopter” to encourage people to move to CPUs that have long term advantage to cloud vendors (even minor power efficiency gains over cloud scale data centers must total significant numbers on the bottom line) and how much of that will be sustained and passed on to users in the long term.

Doubtless we’ll land somewhere in the middle.

Question I have now is: where the heck is Azure in all this? Between Lambda and ARM on AWS its hard not to feel as if the portability advantages, both processor and OS, of .NET Core / 5 are being realised more effectively by Amazon than they are by Microsoft themselves. Strange times.

Full Results

Response Times (ms)
TestInstanceClients per secondMinMaxAverageSuccessful ResponsesTimeouts
Mandelbrott4g.micro (ARM)2618751638600
Mandelbrott4g.micro (ARM)5765279417091320
Mandelbrott4g.micro (ARM)10761695838821300
Mandelbrott4g.micro (ARM)157591020357041271
Mandelbrott4g.micro (ARM)2080210207745911914
Mandelbrott3.micro (x64)2701885744600
Mandelbrott3.micro (x64)5878331320691080
Mandelbrott3.micro (x64)10855803744981030
Mandelbrott3.micro (x64)15973102026930849
Mandelbrott3.micro (x64)2010301021584957435
Mandelbrott2.micro (x64)267511401010580
Mandelbrott2.micro (x64)565153243332720
Mandelbrott2.micro (x64)101867101936999568
Mandelbrott2.micro (x64)1514451020394583244
Mandelbrott2.micro (x64)2014861020688951140
Asynct4g.micro (ARM)202363712756000
Asynct4g.micro (ARM)50222417837314980
Asynct4g.micro (ARM)10023141428629940
Asynct4g.micro (ARM)2003101738820283995200
Asynct3.micro (x64)202334022796000
Asynct3.micro (x64)50235491240714980
Asynct3.micro (x64)10023554529229940
Asynct3.micro (x64)2002341737625983089260
Asynct2.micro (x64)202424122986000
Asynct2.micro (x64)5024154531214970
Asynct2.micro (x64)1002449829226019890
Asynct2.micro (x64)2003471737538582118252

Code has no value

Nor does SCRUM.
Nor does XP.
Nor does waterfall (I could keep going with the methodologies).
Nor does unit testing.
Nor does acceptance testing.
Nor does the runtime you are using.
Nor does the version of a framework you are using.
Nor does your automated deployment process.
Nor does the language you are using.
Nor do the patterns you are using.
Nor do those post-its you’ve put on the wall.

We get so worked up about these things on Twitter (and boy do we get worked up) but they are all intrinsically valueless. Utterly valueless.

What gives them value (positive or negative) is context – your goal and your constraints. To illustrate this I’m going to use three examples from my career.

Hobbyist developer

As far as my public persona goes this is largely what I am today. My goal and constraints are pretty clear: I want to have fun, there’s only me and limited time, and I only want to spend hobby levels of money.

In many ways I’m back where I started with my BBC Micro. I have an idea, it appeals to me, and I just crack on and have a bash it. Sometimes the focus on satisfying my curiosity, sometimes its making something I will use, sometimes its making something I hope others will use. I don’t have to talk to anybody about it, I don’t need to make money, I’m the customer / customer proxy.

This being the case I’ll use a toolchain I think is fun and the most complex process I’m likely to have is a Trello board, but mostly not even that. I might add a unit test in if it helps me. I’ll probably do an automated deployment process because the things I do are simple enough that its really just “build and upload”.

I really just make day to day decisions based on what will amuse me.

Small startup

The phrase “next paycheck delivery” was used in what I think was a pejorative manner in a reply to one of my tweets earlier today but actually next paycheck delivery (much like code) has no value in and of itself and can be either good or bad depending on your context. I’ve spent time in a number of startups where actually that was entirely the right thing to be doing – without the next paycheck the company was going to sink.

In the specific example I’m thinking of the business needed to pivot and had very little runway left. We had to attract customers and fast and they wanted to see something working and be able to use it.

So our goal was clear – get a working, usable, demo for a customer. Our constraints: we had an insane deadline, no money, and there were two of us.

We sketched out a plan. Minimum possible set of features based on what would be useful and what we thought it would cost to build. We divided up the work so we could do it in parallel. We wrote no unit tests. We used the most bare bones of process (you do this stuff and I’ll do this stuff). We threw things together in a fairly ramshackle way based on how each of us could move fastest. We were not gnashing out teeth about what version of runtimes we were using. We reused some bits in a different language that had issues but would serve our purpose.

It was so down to the wire we were doing the proverbial “coding while the customer was watching”. Put simply: “I ain’t got time to bleed”.

Did this cause a problem for later down the road? Yes. Absolutely.
Did it enable the business to survive? Yes. Absolutely.
Would it have been better not to end up here? Yes. Absolutely.
Is it code I was proud of? Yes. Absolutely. It met its goal.

I’ve been involved in a few more startups and super constrained environments since and I’ve got a lot better at moving fast and ending up with something that is easier to move along and develop.

If you find yourself here the important thing is to recognise that yes you’re likely building up debt for the future, try to minimise that, understand that at some point yes you may enter rewrite territory.

Large organisation, established products

I’ve also spent time in larger organisations doing various roles – I think the biggest had 50 development teams and it was an environment where, at the wrong time, mistakes in some parts of the system could cost millions in minutes.

The company had market fit, it had customers, it was still growing rapidly, it had complex integrated systems and services, and a physical presence. It didn’t matter if teams did things differently but there were economies of scale that came from solving similar classes of problem in similar ways (why generate unique DevOps pipelines for two .NET API services for example). But what was really hard was co-ordinating all this. Sure we could use versioned APIs, contracts etc. but delivering a significant new piece of value required the delivery from several teams and co-ordination with the physical aspects of the business.

Goals and constraints would change but to focus in on just a handful of key things that were common:

  • Predictable delivery
  • Visibility
  • Common understanding of contracts
  • High quality

As a result we had people focused on service integration and standards, teams followed agile processes (there was variance between the teams), there were two or three runtime environments but some basic conformance standards, a lot of focus on both unit and acceptance testing, formalised roles, code reviews, architecture reviews, discipline specialists, etc. etc.

These things worked for this organisation – in the context of the organisations goals and constraints they added value. “We’re not at home to Mr Cockup”.

See the source image

But the practices they found essential would have crippled the startup trying to pivot.

Finishing thoughts

I provide these examples hopefully to illustrate that its all about context. For me understanding this is what sets a good developer apart from a coder and its vital to engineering management.

To finish in the pithy tone in which I started I just have a few recommendations really:

  • Always make sure you understand and can clearly articulate your goal
  • Always make sure you understand and can clearly articulate your constraints
  • Always make sure you understand what success looks like and how you will measure that
  • Always make sure you understand what failure looks like
  • Always make sure your approach is based on maximizing your outcomes within those constraints.
  • Have an eye on the future but don’t let it impede your today – the future may never come if you do.
  • Relentlessly focus on value – but remember that value is related to your context and not what some dude told you on Twitter.
  • When someone tells you technology, tools, practices have value in and of themselves – they’re probably trying to sell you something.
  • Do not, I repeat, do not become an evangelist unless your job is to be an evangelist. The minute you do you’ve lost objectivity.

Dell XPS 15 Mini-Review – A Developers Perspective

If you follow me on Twitter you’ll know I’m mostly a Mac user and am fortunate enough to have a super nice 16″ MacBook Pro that I definitely consider my benchmark for a good developer laptop. From a usability / tactility perspective the hardware quality is still the best out their in my opinion and you get a lot of power in the box.

However I do like to keep my hand in with Windows 10 and “full” Visual Studio and so like to keep a Windows machine around too. On the one hand having another laptop for this is definitely extravagant… on the other I spent 3/4 of my life with this stuff (and the other 1/4 with my bikes!) and its nice to be able to freshen things up through switching device. My previous Windows laptop was a Surface Book 2 but although at first I was quite impressed over time and with long term use its flaws become obvious.

Firstly Microsoft’s own updates managed to brick the dedicated GPU on two occasions. It would just go missing. And their support teams were hopeless in terms of resolving the actual issue – sure they’d sent me a new unit almost no questions asked (awesome!) but it was clearly a software issue relating to a Windows Update and would likely happen again (their were loads of people effected by it). There have been driver issues with screen detachment. Honestly just not good enough when you own the hardware and operating system and are charging as a premium product.

And finally the hardware itself – its got a great feel to it but their are some glaring issues, particularly in 2020 and with the Surface Book 3 too as its not really been updated. Specifically:

  • The device is unstable on a lap – there’s a lot of weight in the screen so it can tip over easily
  • The trackpad is tiny
  • There are huge borders round the screen

To me, in the price point, all these things make it an absolute no-goer at the price point. I applaud trying something new but the compromises are in all the wrong places for most use cases.

I considered the Surface Laptop range – I really like the tactility aspects, great solid feeling kit, decent trackpad and keyboard. But my word at the prices they are underpowered. In fact they are underpowered full stop.

Plenty of people are fans of them I realise… but to me its like the Apple contingent ignoring the keyboard issues on the laptops or the thermal and expansion issues on the trash can Mac Pro. Obvious issues for premium devices.

So all that leads me to the Dell XPS 15 which I picked up about 3 weeks ago, have done a lot of coding on, and am writing this on. Its an i7 model with the 4K touch screen and 32Gb of RAM and the 1650 GPU. That’s a touch behind my MacBook Pro which is the i9 model with 64Gb of RAM.

Hopefully all the above gives you context for the following comments. Some might be operating system related but unless you want to go Linux the hardware and software go together.

Good Stuff

  • It was really well packaged with a great unboxing experience – Dell have definitely learned from Apple here, first impressions count.
  • Purchase and delivery was easy. The Dell website is a bit “dense” when it comes to how it presents information but I clicked buy and it arrived the next day.
  • The screen is beautiful – it really is. Bright, high pixel density, very thin borders, vibrant, sharp. I love it. I never use it as a touch screen. I hate grubby finger prints on my screens.
  • I’ve not benchmarked it but it feels plenty fast.
  • Plugging it in to my daisy chained Thunderbolt monitor setup via my existing Thunderbolt hub on a single cable “just worked” and it seems to have no trouble running three 4K displays (its own and the two attached)
  • The interior of the laptop feels really nice. Its almost soft and feels more approachable than the now in comparison cold feeling MacBook Pro.
  • Windows 10. I really like Windows 10 – its a nice operating system and things like WSL are neat touches. That said…. see bad stuff too.
  • It handles external displays better than a Mac. Why Apple cannot get this right is beyond me – but when I plug my MacBook in its like a lottery as to what it will decide to do with the screens and its not unusual for me to have to unplug it and try again several times and even restart. The Dell “just works”.
  • I can use XBox Game Pass on it – this has a decent selection of strategy games (my favourite) that I can now play without having to buy them on Steam. Awesome!
  • It has WiFi 6 support – I’ve recently upgraded to a WiFi 6 network to resolve some issues and it was nice to find the Dell has hardware of this standard.

Ok Stuff

  • The trackpad is ok. Its not dreadful and its certainly big enough but clicking it is weird and less accurate than the Mac. It can sometimes feel what I can only describe as “loose”. Gestures are nowhere near as accurate as on a Mac. I’ve got used to it but I don’t like it.
  • The keyboard is ok. Again not dreadful but not quite to my taste (for context I really like the Apple magic keyboard and the reworked and fixed keyboard on the new MacBook’s). It just feels a bit… squidgy?
  • The outside of the device isn’t very attractive to my eyes. Sat next to my MacBook it looks like a cheap device – it is not a cheap device!
  • Battery seems ok. I’ve never found myself desperate for power.

Bad Stuff

  • The fans are noisy when working hard and it seems to spin fans up more readily than the MacBook Pro. Relatedly it also seems to get hotter easier than the MacBook.
  • Windows 10 spyware. Not being able to disable telemetry is, and excuse me, a fucking disgrace.
  • The out the box Windows 10 experience is terrible. After initial boot up there were loads of updates to apply. The Store was only partially working until one of the updates downloaded. Worse it does all these downloads in bits and pieces with many reboots and no end in sight to it. Its confusing and incredibly user hostile. I don’t mind an update but gather them together and do a single update – and if the operating system is only going to partially work until this is done hold off until it can be done. Rubbish frankly. Utterly rubbish.

Overall

I like it! Interestingly I’m finding myself preferring the Dell, and Windows 10, when plugged in at my desk and preferring my MacBook when on the move and thanks to WSL I don’t have to grapple with different shells. When at my desk I’m using an Apple Magic Keyboard and Logitech MX Master mouse and so that is certainly part of it.

I don’t (and won’t again) do app development so if Apple messes up with this ARM transition or we end up with borked lower power laptops as a result I feel I’ve got a decent way forward with hardware. At the moment I feel as if I could happily use this as my daily driver.

All this said – longer term problems may emerge. I guess I’ll find out.

Contact

  • If you're looking for help with C#, .NET, Azure, Architecture, or would simply value an independent opinion then please get in touch here or over on Twitter.

Recent Posts

Recent Tweets

Sorry, that page does not exist.

Recent Comments

Archives

Categories

Meta

GiottoPress by Enrique Chavez