Skip to main content

Ralsina.Me — Roberto Alsina's website

Benchmarking Markdown in Crystal

I am work­ing a bit (s­low­ly) on Nicol­i­no a stat­ic site gen­er­a­tor writ­ten in Crys­tal. One of the things it does is, it ren­ders mark­down files.

Since the mark­down us­age is lim­it­ed to, like, 3 lines, I thought "Why not try all the mark­down li­braries and see which one is faster?"

So, I did.

The bench­mark is sim­ple:

  • An emp­ty Nicol­i­no site.
  • 4000 sim­ple mark­down files (a few lorem ip­sum para­graph­s)
  • Nicol­i­no com­piled in re­lease mode
  • Ren­der the whole site 10 times, av­er­age the last 7 runs
  • De­fault con­figs for ev­ery­thing

All this on this ma­chine:

        a8888b.           Host        -  ralsina@mindy
       d888888b.          Machine     -  Micro Computer (HK) Tech Limited Default string UM560 XT
       8P"YP"Y88          Kernel      -  6.9.9-arch1-1
       8|o||o|88          Distro      -  EndeavourOS
       8'    .88          DE          -  Qtile
       8`._.' Y8.         Packages    -  1340 (pacman), 1 (cargo), 18446744073709551615 (Homebrew)
      d/      `8b.        Terminal    -  zellij
     dP        Y8b.       Shell       -  fish
    d8:       ::88b.      Uptime      -  10d 18h 7m
   d8"         'Y88b      CPU         -  AMD Ryzen 5 5600H with Radeon Graphics (12)
  :8P           :888      Resolution  -  4096x2160, 1920x1080
   8a.         _a88P      CPU Load    -  15%
 ._/"Yaa     .| 88P|      Memory      -  9.2 GB/24.5 GB
 \    YP"    `|     `.
 /     \.___.d|    .'
 `--..__)     `._.'

Of course Nicol­i­no does some oth­er things be­sides ren­der­ing mark­down, so to iso­late that part I ran a cou­ple of dum­my im­ple­men­ta­tion­s:

  • NOOP It does noth­ing. When asked to com­pile a string to mark­down, it re­turns the same string.
  • TOEMP­TY It does less than noth­ing. When asked to com­pile a string to mark­down, it re­turns an emp­ty string.

So, the dif­fer­ence be­tween emp­ty and noop is prob­a­bly what it takes to ren­der some tem­plates and store the out­put in disk.

Then I ran the bench­mark for the 5 mark­down li­braries I could find:

Here is a chart show­ing the times in sec­onds for each li­brary with­out the time that is used in oth­er things (NOOP's time, which was 2.62 sec­ond­s).

Markdown libraries benchmark 0011223344550.3898.3446153846154505.25393356643360.66201.3723076923077491.63942307692310.33304.40000000000003507.68509615384620.38407.4276923076924505.25393356643365.28510.4553846153847267.0Markdown libraries benchmarkmarkdcr-discountcrystal-cmarkcr-mark-gfmluce

So, luce is much, much, much slow­er, and crys­tal-c­mark is the fastest. And cr-dis­count (MY OWN BIND­ING) is much slow­er than the oth­er­s, which is a bit dis­ap­point­ing.

On the oth­er hand, there are two sides to op­ti­miz­ing. One is choos­ing the fastest li­brary, the oth­er is not car­ing if the dif­fer­ence is small in ab­so­lute, even if it's large in rel­a­tive.

What does that mean?

This is over 4000 doc­u­ments.

So, while cr-dis­count is slow­er, it's still ren­der­ing 4000 doc­u­ments in 0.66 sec­ond­s. That's 0.000165 sec­onds per doc­u­men­t.

That's 1.65 tenths of a thou­sand of a sec­ond. That's over 6000 doc­u­ments per sec­ond.

If the nor­mal use­case was to ren­der thou­sands of doc­u­ments, then that would make a dif­fer­ence. But it's not. It's usu­al­ly 3 doc­u­ments.

So, as long as cr-dis­count has any fea­ture I need and it's not in the oth­er li­braries, it's fine to use it, and the same goes for the oth­ers (ex­cept luce, I guess, but stil­l: 757 doc­u­ments per sec­ond is not bad).

Update: Compiling discount with -O3 brings it down to 0.48, which is bettern than 0.66 but makes no difference for the conclusions.

30-minute project: caddy-static

I host a num­ber of stat­ic web­sites on my per­son­al serv­er. Over the years, they have been served by a va­ri­ety of web server­s, but I re­cent­ly de­cid­ed I want­ed to make it easy.

So, I looked for a mod­ern and min­i­mal­is­tic web serv­er that sup­port­ed a fea­ture of the ven­er­a­ble thttpd

That fea­ture is au­to­mat­ic vir­tu­al hosts. The main idea is that you have a fold­er and in there a bunch of sub­fold­er­s, each one is a site. And the name of the fold­er is the name of the site.

So, if I have /srv/ralsina.me then when a user asks for http://ralsina.me they get that. And if I have /srv/whatever.com and the user asks for whatever.com they get that.

Al­so, if a site is reach­able with more than one name, then just add sym­links and it should just work.

I found a way to do it us­ing Ng­inX but that's far from min­i­mal­is­tic.

So, I looked around and found that Cad­dy was pret­ty close ex­cept it did­n't do the au­to­mat­ic stuff.

So, I wrote the miss­ing bit, threw ev­ery­thing in a dock­er con­tain­er and now I have a 30-minute project that serves all my stat­ic sites.

Here's the dock­er com­pose I am us­ing:

version: "3.8"
services:
  web:
    logging:
      driver: journald
      options:
        tag: web
    container_name: web
    stdin_open: true
    tty: true
    ports:
      - 8080:8888
    volumes:
      - /data/websites/:/srv/
    image: ghcr.io/ralsina/caddy-static-arm64:latest
    restart: always
networks: {}

And here's the pro­jec­t: cad­dy-stat­ic

It probably needs some tweaking for anyone else specially in the Caddyserver config and maybe in the template it uses to generate sites which is hardcoded.

If any­one ac­tu­al­ly us­es this I can make those things con­fig­urable.

The Beauty and Horror of Crystal Shards

I have been us­ing a lot of Crys­tal for per­son­al projects for about a year. Think of it like "na­tive Ru­by with type in­fer­ence" or some­thing like that.

But this post is not about the lan­guage it­self, it's about one part of its ecosys­tem: the shard­s.

The Nice

Shards are like Ru­by gem­s, or like what­ev­er go calls them nowa­days, or rust crates, just "li­braries" that are writ­ten most­ly in crys­tal.

The thing is ... they are awe­some!

Suppose you want to use markdown in your app. You go to shards.info which crawls GitHub and finds things written in Crystal. You search for markdown and you find a few that look good. Suppose you want to use my cr-discount shard.

You just add the shard to your shard.yml and use it, like this:

dependencies:
  cr-discount:
    github: ralsina/cr-discount

Then in your code:

require "cr-discount"

markdown = "This *is* **markdown**"
html = Discount.compile(markdown)

And that's it, you just in­te­grat­ed it in­to your build, your code is us­ing it, and that's all there is to it.

But not on­ly that, sup­pose you are us­ing a shard like docr but you find a tiny bug or two. You fork it, fix the bugs and cre­ate a PR. If the au­thor is ac­tive, they will merge it, and you can go back to us­ing the shard, but even if they aren't ... you can just use your fork in the mean­time.

Just use ralsina/docr instead of marghidanu/docr in your shard.yml and you are using my fix.

And there's more! Sup­pose you find some code in your projects that you keep re­peat­ing. For ex­am­ple, I al­ways use the same log­ging set­up in CLI app­s:

  • Col­or­ful if it makes sense
  • Con­fig­urable ver­bosi­ty
  • Er­rors and worse in stderr
  • In­fo and bet­ter in std­out

So, why copy that ev­ery­where? Just make it a tiny shard.

There is no overhead in your code, you don't have to ask users to install anything, you just add it to your shard.yml and it's there for everyone that cares.

The Not So Nice

Of course noth­ing is free of cost.

  • Be­cause it's easy to cre­ate shard­s, it's easy to aban­don shard­s.
  • Be­cause it's easy to fork shard­s, it's easy to at­om­ize the ecosys­tem.
  • Be­cause it's easy to find shards and it's de­cen­tral­ized, it's triv­ial to poi­son the sup­ply chain (although TBH it seems to be easy enough in any lan­guage).

So, you have to be care­ful. You have to check if the shard is main­tained, if it's the best one for the job, if you fork it you need to com­mit to keep­ing your fork work­ing, you al­ways need to push the PR even if the au­thor does­n't pick it up be­cause it's there for oth­er user­s.

So, con­clu­sion­s... they are what they are. For me they are the most prac­ti­cal thing ev­er and I of­ten wish Python had some­thing like them, but they are al­so quite ... scary? And see­ing the aban­doned shards makes me sad for a lan­guage that should be much more pop­u­lar than it is.

Schizo Desktop IV: Audio

I have an of­­fice. It's like a home of­­fice but it's in an­oth­er place. It's just mine, so I get the peace and qui­et of a home of­­fice but al­­so get to go out­­­side to get there. It's a good set­up.

BUT I have two com­put­ers there. Well, ac­­tu­al­­ly I have like 10, but I in­­ter­act with two. One is my per­­son­al desk­­top com­put­er, the oth­­er is my work lap­­top. and I want to use the same pe­­riph­er­als in the same way with both of them. That's why I have the world's most com­­pli­­cat­ed schizo desk­­top set­up.

This se­ries of posts will doc­u­­ment it, the why and how of it, and the var­i­ous things I've learned along the way, along with mak­ing you want to buy weird chi­­nese gad­get­s.

Today: Audio

As in the rest of this se­ries, some­thing that usu­al­ly is very sim­ple be­comes a bit com­pli­cat­ed be­cause I want to use the same pe­riph­er­als with two com­put­er­s.

In the case of au­dio it's even worse be­cause I want to use dif­fer­ent de­vices de­pend­ing on con­tex­t.

The Requirements

  • I want to list­ed to both com­put­ers with­out head­set­s, be­cause I don't like wear­ing head­sets for long.
  • I want to be able to lis­ten us­ing head­sets if I want to, to take ad­van­tage of noise can­celling and to be qui­eter.
  • I have a good mi­cro­phone, I want to use. I don't want to use the bad ones, like my we­b­cam's, or my note­book's.

The Hardware

  • Speak­er: a lar­gish speak­er with a 3.5mm jack, hang­ing from the bot­tom of my desk, in­vis­i­ble.
  • Mi­cro­phone: a XLR con­denser mi­cro­phone (means it needs 48v phan­tom pow­er!)
  • Head­set: Blue­dio Hur­ri­cane H2 (with ca­ble!)

The head­set and mi­cro­phone switch com­put­ers as de­scribed in a pre­vi­ous post BUT how does a XLR mi­cro­phone con­nect to a com­put­er?

You need an au­dio in­ter­face. I have a su­per cheap chi­nese one:

The v8 live sound card

This plugs to a com­put­er via USB and works as a sound card, with in­put and out­put.

BUT it's not just a sound card, it's a mix­er. It has con­nec­tors for:

  • 2 Con­denser mi­cro­phones (yes, with phan­tom pow­er)
  • 2 "Ac­com­pa­ny" in­put­s, which are stereo line in­puts
  • Blue­tooth in­put
  • Mon­i­tor: this is a stereo out­put that is a mix of all in­put­s, so you can lis­ten to ev­ery­thing that's go­ing on.

It has vol­ume con­trols for "ac­com­pa­ny" / "mic" / "mon­i­tor" and a cou­ple nice LED feed­back light­s.

It al­so has a bunch of ef­fect­s, like re­ver­b, echo, sil­ly sound­s, etc. which I don't use.

That au­dio in­ter­face is the hub of my au­dio set­up. Usu­al­ly it's con­nect­ed to my work com­put­er with the mi­cro­phone, but I can switch it over to my per­son­al PC.

My per­son­al PC is con­nect­ed to it via BT, and the mon­i­tor out­put is con­nect­ed to the speak­er.

The head­set is a sep­a­rate usb de­vice for per­son­al pref­er­ence.

The Software

Not much need to do any­thing spe­cial with two ex­cep­tion­s:

  1. Dis­able all the au­dio in­ter­faces I don't want to ev­er use.
    • HD­­MI au­­dio out­­puts
    • We­b­­cam's mi­cro­­phone
    • Head­­set's mi­cro­­phone
  2. Use rofi-­sound-pick­er to choose where the sound goes. I use it most­ly to switch from speak­er to head­phones.

Rofi Sound Picker

The Routing

In work mod­e:

  • Both PCs sound via the au­dio in­ter­face and the speak­er con­nect­ed to its mon­i­tor out­put:
    • Per­­son­al PC via BT
    • Work PC via USB
  • Mi­cro­phone is con­nect­ed to the au­dio in­ter­face and is used by the work PC (and se­lect­ed in team­s/­zoom/etc as in­put)
  • Head­set is con­nect­ed to the work PC and I can change to it us­ing the rofi-­sound-pick­er

In Per­son­al mod­e:

The same as work mod­e, un­less I want to record some­thing, in which case I switch all the au­dio to the per­son­al PC and just pick and choose.

Conclusion

This is worth it even if all I got was both PCs com­ing out of the speak­er. The rest is good and a bonus.

Schizo Desktop III: Monitors

I have an of­fice. It's like a home of­fice but it's in an­oth­er place. It's just mine, so I get the peace and qui­et of a home of­fice but al­so get to go out­side to get there. It's a good set­up.

BUT I have two com­put­ers there. Well, ac­tu­al­ly I have like 10, but I in­ter­act with two. One is my per­son­al desk­top com­put­er, the oth­er is my work lap­top. and I want to use the same pe­riph­er­als in the same way with both of them. That's why I have the world's most com­pli­cat­ed schizo desk­top set­up.

This se­ries of posts will doc­u­ment it, the why and how of it, and the var­i­ous things I've learned along the way, along with mak­ing you want to buy weird chi­nese gad­get­s.

Today: Monitors

I have at least three mon­i­tors for my two com­put­er­s. It can get more com­pli­cat­ed than that, but let's just lim­it this to the "ba­sic­s".

Be­cause one of the com­put­ers (my work one) is a note­book, it has its own mon­i­tor. Then I have two reg­u­lar mon­i­tors, both 1080p, one of them land­scape and the oth­er por­trait.

This is how that look­s.

The three monitors described above

This would nor­mal­ly be just a mat­ter of plug­ging them in the right com­put­er, but ... in my case there is no right com­put­er!

When I am work­ing, I want the mid­dle mon­i­tor to be con­nect­ed to my work lap­top, and when I am not, I want it con­nect­ed to my desk­top.

The right­most mon­i­tor I al­ways want for my desk­top (usu­al­ly play­ing mu­sic or videos while I work) and of course the lap­top keeps con­trol of its own screen.

Ad­di­tion­al­ly I want to be able to spo­rad­i­cal­ly con­nect ran­dom com­put­ers (like rasp­ber­ry pis) and have them show up on a mon­i­tor.

Now it's not so ob­vi­ous, right?

Enter The HDMI Matrix

This is a 4x2 HD­MI ma­trix. It has 4 in­puts and 2 out­put­s. That means it can get 4 dif­fer­ent video sig­nals and will route them to 2 dif­fer­ent mon­i­tors in a com­plete­ly ar­bi­trary man­ner.

  • Want in­put 1 in both out­put­s? Sure.
  • Want in­put 1 in mon­i­tor 1 and in­put 2 in mon­i­tor 2? Sure.
  • Want it the oth­er way around? Sure.

It's to­tal­ly over­pow­ered for what I need, which could be done with a sim­pler 4x1 switch, but hey, it's nice.

So, I have 4 in­puts con­nect­ed:

  1. My desk­top com­put­er
  2. My desk­top com­put­er
  3. My work lap­top
  4. A ca­ble that I can reach un­der the desk­top

The out­puts are con­nect­ed to the mid­dle and right­most mon­i­tors in the desk re­spec­tive­ly.

When I work, the con­fig­u­ra­tion is in­put 3 in the mid­dle mon­i­tor, in­put 1 in the right­most mon­i­tor.

When I am not work­ing, it's in­put 2 in the mid­dle mon­i­tor, in­put 1 in the right­most mon­i­tor.

When I have a rasp­ber­ry I want to look at for a mo­men­t, that's in­put 4 to the mid­dle mon­i­tor.

Of course there is an­oth­er prob­lem:

The por­trait mon­i­tor does­n't know it's on its side. So ev­ery­thing is ro­tat­ed 90 de­grees when shown there.

Enter AutoRandr

Au­toRan­dr is one of those tools once you have it and use it you can't live with­out it.

It saves the cur­rent mon­i­tor con­fig­u­ra­tion and can re­store it when­ev­er it sees the same mon­i­tors plugged in­to the same out­put­s.

So, just make it look nicein "work mode", and configure all the monitor rotations and so on, then autorandr --save work.

Then, when you are not working, make it look nice again, and autorandr --save home.

Have to do that in both com­put­er­s, of course.

From then on, when­ev­er the switch changes things around, au­toran­dr au­to­mat­i­cal­ly re­con­fig­ures the mon­i­tors the right way on both com­put­er­s.

The on­ly prob­lem is that to switch from home con­fig to work I would have to do things like "click on the B but­ton of the ma­trix thing un­til is says "3", then click on the A but­ton un­til it says "1" and it's bor­ing.

Enter the Remote

As you can see in the pre­vi­ous pic­ture, the ma­trix has a re­mote. It's a sim­ple re­mote, with 8 but­ton­s, one for each in­put/out­put com­bi­na­tion.

I could just use the re­mote to switch be­tween work and home con­fig­u­ra­tions, but that's bor­ing too.

So, I use soft­ware to drive an in­frared emit­ter that switch­es things around when I click a but­ton in the macro key­board or flip a switch on a web­page.

But that's for an­oth­er post.


Contents © 2000-2024 Roberto Alsina