HNHacker News
TopNewBestAskShowJobs

staticfloat

405 karma · joined August 27, 2013

submissionscomments
staticfloat··on DigitalOcean's Hacktoberfest Is Hurting Open Source
No need to be shy! Quality work is quality work, no matter when it is submitted, and earlier is usually better! If DO is going to give out free t-shirts, there's no reason to purposefully deprive yourself of them just because someone wrote a blog post.

The Julia Language [0] gets some spammy issues/pull requests as well (not only during October) and while we have the benefit of dozens of maintainers such that it's bearable, I definitely sympathize with the issues OP is dealing with. Opt-in could be a good idea, although as usual, the issue is scaling and verification.

[0] https://github.com/JuliaLang/julia

staticfloat··on The missing harm of manual dispatch in Julia
While it's true that all code is typed (e.g. the compiler knows what types to expect at different points throughout the program), it's completely possible to have essentially "worthless" types computed. E.g. if I have the following:

    foo(x::Int) = x + 1
    foo(x::Float64) = x + 2
    foo(x::String) = "$(x) $(x)"

    function eval_user_input()
        user_input = readline(stdin)
        user_obj = eval(Meta.parse(user_input))
        return foo(user_obj)
    end

Then clearly there is no way the compiler can know what the type of `user_obj` is, however it will do its best to infer the entire function. We can use `@code_warntype` to quickly and easily find places where Julia's type inference gives a suboptimal result (which is often a source of performance issues, since there will be runtime overhead in those cases).

    julia> @code_warntype eval_user_input()
    Variables
      #self#::Core.Compiler.Const(eval_user_input, false)
      user_input::String
      user_obj::Any

    Body::Union{Float64, Int64, String}
    1 ─      (user_input = Main.readline(Main.stdin))
    │   %2 = Base.Meta.parse::Core.Compiler.Const(Base.Meta.parse, false)
    │   %3 = (%2)(user_input)::Any
    │        (user_obj = Main.eval(%3))
    │   %5 = Main.foo(user_obj)::Union{Float64, Int64, String}
    └──      return %5

You can see that `user_obj` is annotated as type `Any`, and that invoking `foo()` is said to itself return a type of `Union{Float64, Int64, String}`, which is to say, it has no idea which `foo` it's going to call ahead of time. I will note that in the REPL, all the suboptimal stuff is highlighted in red, making this a very nice debugging tool for immediately finding poorly-inferred sections of code. If we ask the compiler to run this code through its optimization passes as well by passing the `optimize=true` flag to `@code_warntype`, we can even see how the compiler tries to inline `foo` by turning the call to `foo()` into a series of `isa()` conditionals, checking the type of the return from `eval()`. I'm pasting the relevant section here, for the full example you can see the screenshot linked below, showcasing the red highlighting as well:

    10 ┄ %20 = φ (#8 => %12, #9 => %18)::String
    │    %21 = Base.Meta.parse::Core.Compiler.Const(Base.Meta.parse, false)
    │    %22 = invoke Base.Meta.:(var"#parse#4")(true::Bool, true::Bool, %21::typeof(Base.Meta.parse), %20::String)::Any
    │    %23 = Main.eval(%22)::Any
    │    %24 = (isa)(%23, String)::Bool
    └───       goto #12 if not %24
    11 ─ %26 = π (%23, String)
    │    %27 = invoke Base.string(%26::String, " "::String, %26::Vararg{String,N} where N)::String
    └───       goto #17
    12 ─ %29 = (isa)(%23, Float64)::Bool
    └───       goto #14 if not %29
    13 ─ %31 = π (%23, Float64)
    │    %32 = Base.sitofp(Float64, 2)::Float64
    │    %33 = Base.add_float(%31, %32)::Float64
    └───       goto #17
    14 ─ %35 = (isa)(%23, Int64)::Bool
    └───       goto #16 if not %35
    15 ─ %37 = π (%23, Int64)
    │    %38 = Base.add_int(%37, 1)::Int64
    └───       goto #17

[0] https://i.imgur.com/DZPWUvi.png
staticfloat··on JuliaCon2020: Julia Is Production Ready
Yep, to build on Stefan's sibling comment, we deploy a few services through docker that follow the flow of <merge on github> -> <build on dockerhub> -> <deploy through watchtower> that has been working for us really well.

Docker has some nice infrastructure that we like, such as the ability to set memory limits, automatically restart processes on crash/if a healthcheck goes sour/on reboot, easily spin up different versions of the same service (based on which image you tag to a name), etc... Containerization has done a lot for our ability to bring servers up onto heterogenous hardware easily and quickly.

As an example, this repository [0] is deployed on 10 machines around the world, providing the pkg servers that users connect to in geographically-disparate regions. The README on that repository is a small rant that I wrote a while back that walks through some of the decisions made in that repository and why I feel they're good ones for deployment.

To answer your specific questions:

* How to deploy Julia: we use docker containers and watchtower [1] to automatically deploy new versions of Julia.

* Keep dependencies sane: it's all in the Pkg Manifests. We never do `Pkg.update()` on our deployments, we only ever `Pkg.instantiate()`.

* Pin memory usage: We use docker to apply cgroup limits to kill processes with runaway memory usage [2]. This is only triggered by bugs (normal program execution does not expect to ever hit this) but bugs happen, so it's good to not have to restart your VM because you can't start a new ssh session without your shell triggering the OOM killer. ;)

[0] https://github.com/JuliaPackaging/PkgServerS3Mirror [1] https://github.com/containrrr/watchtower [2] https://github.com/JuliaPackaging/PkgServerS3Mirror/blob/c6a...

staticfloat··on Julia 1.5 Highlights
I see noone has answered your technical question yet, so I'll give (one) reason why this is difficult in the land of Julia.

A fundamental idea in Julia is Multiple Dispatch; methods are extended many times with different types, and common "verbs" (such as `open()`, `write()`, etc...) are used to operate upon a wide variety of objects (files, sockets, plot handles, sound devices, etc...). Exactly which pieces of code gets called when you call `open(foo)` depends not only on the type of `foo`, but also on what methods have been defined for `open()`.

As a concrete example, the deep learning library `Flux.jl` has code within it that checks to see if CUDA packages are already loaded or not within it. If they have been loaded, then `Flux.jl` will load its GPU support libraries as well. This means that:

``` using CUDAnative using Flux ```

and

``` using Flux using CUDAnative ```

Result in different environments, and hence, different code being run. It is unfeasible to precache all packages (you would, in the worst case, end up with a combinatorial explosion of packages that is `N!` in number of distinct package-package version tuples) and intelligent ways of thinning that down would quickly become untenable due to the highly dynamic nature of Julia.

Really what this is working around is the lack of support in the Julia package ecosystem for a way for packages to coordinate behavior; with stronger systems in place for a package environment to say "Hey, this is a GPU-using environment, and all packages should turn on GPU support!" there wouldn't be a need for this kind of dynamic behavior.

This is an example of something that happens all the time in the Julia world; when we lack a proper structure, users hack their way out of it with crazy dynamic code that does things that the language supports. We don't want to take that dynamic power away because that's where our edge over many other languages comes from, but it does make it challenging for the compiler to support everything in as low-latency a manner as a less-dynamic language would allow.

There are plenty of possible workflow improvements, of course, such as tighter PackageCompiler.jl and Revise.jl integration that would allow you to bake system images per-environment while still picking up some code changes, etc... but the penalty paid is pretty high; right now, building a system image takes many minutes, and it would be done for any kind of code change. There is compiler work underway to allow for more incremental system image compilation as well, but as with many things on our todo list, it must find its way into the compiler team's pipeline alongside all of the other cool things that are being worked on.

As mentioned in other comments, even without changing the workflow of Julia projects at all, incredible strides have been made over the last few releases, but I wanted to give you an idea of why certain things are harder than they might seem, when coming from other languages. As an aside, `Plots.jl` and `DifferentialEquations.jl` are two notoriously slow-to-load packages, beaten out only by other behemoth packages such as `Flux.jl` or `Turing.jl`.

staticfloat··on Julia 1.5 Highlights
The PkgServer is an open source caching server, available here: https://github.com/JuliaPackaging/PkgServer.jl

The PkgServer protocol serves content-addressed chunks of data to Julia Pkg clients, so to download a certain version of a package, the Pkg client will request things like `GET /package/${pkg_uuid}/${content_hash}`. If you can pre-fill a PkgServer with all of the packages that you want, then it can serve a client just fine.

The full design is that there are a small number of "Storage Servers" that continually explore the global registry of packages (called `General`, located here: https://github.com/JuliaRegistries/General), downloading and storing tarballs for every version of every package that is available. These storage servers store everything forever, while Pkg servers contain an LRU cache to allow them to be deployed close to whatever compute resources will be requesting packages. For an airgapped solution, you could generate a static snapshot of some selection of the packages you want to serve, then serve them with nginx and point to that "static storage server" with the opensource Pkg Server, and it would all "just work".

An example of how to generate a static storage server is here: https://github.com/JuliaPackaging/PkgServer.jl/blob/master/b..., you would serve the resultant directory structure with something like nginx, then point the Pkg Server to that server as the upstream storage server.

I will note that Julia Computing offers an enterprise solution for dealing with secure/restrictive environments called JuliaTeam which provides this in a convenient, managed bundle, along with many other useful features.

EDIT: Ah, I forgot to mention, Stefan and I gave a talk at JuliaCon 2020 that touches on some of this, here's a link to the timestamp of the relevant section: https://youtu.be/xPhnJCAkI4k?t=350

And for more info, here's the original planning issue (note some things have changed as we've implemented it over the last year, but the bones are the same): https://github.com/JuliaLang/Pkg.jl/issues/1377#issue-492482...

staticfloat··on XXH3, world's fastest hash algorithm, has reached stable status
If you look at the second table below the first, an AVX2 implementation of XXH3 gets just under a 2x speed boost.
staticfloat··on With cops away, it’s like Capitol Hill is slipping back to the Pinkerton’s era
I live nearby and visited on June 14th for a few hours, at around 3pm. I note the time because the conclusions I came to during my time there are dependent both on the time of day, as well as the date.

First off, while I was there, it was an overall friendly, bustling atmosphere. There were no armed security forces, no armed thugs demanding protection money from businesses, etc... It felt more like the Folklife festival; there were parents with their kids, people doing street art, and stalls selling food. While most people were wearing masks, there was also a distinct lack of social distancing. My first impression was that this was a plausible excuse for people to be outside and in a large, social situation. It was a beautiful sunny day, of course people want to be outside.

There was a central event area where there were some kind of organized speeches/events. While I was there, a speaker was talking about the unjust seizure of land in the Seattle area from an indigenous people group, and after that there was a musical performance from members of that indigenous group. All around the area there were solicitors urging the passers by to sign petitions and vote on various issues. The craziest things I saw were people digging up Cal Anderson park lawn spaces to create vegetable patches. There was an incredible amount of graffiti all over the place, a high percentage of it anti-cop. There were stalls with free granola bars/water/other snackish things and a few pieces of first aid equipment, all labeled as "supplies for protesters".

What follows are my opinions and interpretations:

I do not believe the CHAZ/CHOP had any real impact on the political discourse of Seattle. There was heavy protesting and rioting beforehand in the Capitol Hill area up to the retreat of the Police from that precinct (for those unfamiliar with Seattle political geography, Cap Hill is an epicenter of progressive/liberal culture) and the retreat of the police was a very good move from the government's standpoint. By giving a very small area of land over to "the protesters" (whatever group may take that mantle upon themselves) the city government did two things: it successfully redirected attention away from the police and the issue at hand, and it allowed the protest movement to collapse under its own weight.

I was struck by the lack of focus of the protest movement; while the police were pushing back against the protests, there was a very clear message being sent through social media, the traditional media, and in the channels of political discourse: The current level of police violence is unacceptable. However just a few days after the CHAZ/CHOP was born, a large number of tangentially-related concerns bubbled to the surface. While I was there, I was approached by solicitors that wanted to pass better environmental regulations, pass legislation to protect trans teens, and institute new taxes on large corporations (e.g. Amazon). All three were motivated in some way ("POC communities are usually the first to suffer in environmental disasters, to protect these communities we must protect the environment", or "how will we pay for new social programs? Tax Amazon!") but it was very clear to me that without keeping a strong focus on changing policies to suppress police violence, outsiders of this movement would be enabled to glance into the cauldron of demands and simply state "the socialists are yelling again".

With regards to the shootings, I can say that while I nor anyone I know who went to the zone personally saw firearms, I know they were there at some point, and there were people with more firepower than sense taking advantage of the lack of cops to do whatever they wanted. I can see the tone of the area changing drastically between 3pm and 3am (which is when some of the shootings occured) and while I am certain the lack of police presence did not help matters, Seattle does have shootings every now and then. It's not immediately clear to me how much of the shootings is or is not due to lack of police.

I have not been to capitol hill since the police moved back in; from what I have heard from other people who have been there, things are more or less "normal" now, just with more graffiti and street art. I don't know what's going to happen to the vegetable gardens. ;)

staticfloat··on Ryzen Threadripper Pro 3995WX Spotted
One big difference is memory availability; the amount of RAM directly accessible from a 64-core CPU is much larger than that of the thousands of CUDA cores on a Quadro card. Perhaps for many workloads intelligent use of the PCIE bus can make up for that, streaming datasets in/out of the card, but for others, having random access to hundreds of GBs of data may be non-negotiable.

I'd be interested to know what workloads truly require that; most simulation solvers that I can think of that require large amounts of RAM are working on a discretized spatial grid, and the physical laws they're simulating are fairly "local"; e.g. you can process one area then move on to the next, in a nice stencil operation. This lends itself well to both parallelization and a sort of data-locality, so you can stream the necessary pieces of data in to the cores in an orderly fashion. I don't know what kinds of massively parallelizable workloads require much more random access of memory.

staticfloat··on Pkg.jl telemetry should be opt-in
The GDPR explicitly allows for the processing of personal information without consent in the event that such processing is required for ensuring network security and availability, see [1], [2] and [3] for more reading on this. Note that I am not a lawyer, and you should consult a lawyer (as we did) to ensure that all policies fall within GDPR laws.

That is precisely what the logged IP addresses are used for (an example: nginx access logs), and is one of the reasons why we would much rather use a random number generated by the client machine than an IP address; because the bits themselves have no meaning, unlike IP addresses.

As mentioned in the linked thread, NumFocus has worked with a legal team that specializes in this type of law, this plan is all in compliance with the GDPR.

[1] https://gdpr-info.eu/recitals/no-49/ (The actual GDPR text regarding security concerns) [2] https://blogs.akamai.com/2018/08/dispelling-the-myths-surrou... (Akamai legal team confirming that this interpretation of logging IP addresses for security purposes is valid) [3] https://law.stackexchange.com/a/28609 (Stack exchange post pointing out that even more exceptions exist beyond just security)

staticfloat··on Pkg.jl telemetry should be opt-in
We do have a limited retention policy for the package server logs we keep (which include client IP addresses). It's not publicly stated anywhere right now, but one reason why we need to keep IP addresses is for abuse mitigation. We have been hit in the past by users that do things like download large (100MB+) files from our package cache servers multiple times a second for days on end. This is a particularly easy case to catch (since it easily pops to the top of any analysis you'd care to run, across any timespan) but there are more subtle forms that require a longer time window of analysis (e.g. users that download once per hour, all month) that would be lost in the noise without the ability to see what's going on.

This comment is not meant to serve as an official policy, just pointing out one of the reasons why we can't delete IP addresses like 1.1.1.1 and 8.8.8.8 do; because the abuse vectors for a server that serves the community large resources is very different from that of a DNS server.

Most of the "abuse" we see is not malicious in nature, but is instead users that have some kind of very poorly-configured autoinstaller on a cluster. In the case of a catastrophic issue like the one mentioned above, we null-routed the IP address, reached out to the abuse contact for that IP, and worked with the user to architect a better system. Everyone is happy now, and we can continue to provide a high quality service for the community without breaking the bank.

staticfloat··on Rust: Dropping heavy things in another thread can make your code 10000x faster
It seems that this would be a great reason to not pass the entire heavy object through your function, and to instead pass it as a reference. When passing an object (rather than a reference to an object) there's a lot more work going on both in function setup, and in object dropping. I'm not a rust guru, so I don't know the precise wording, but it's simple enough to realize that if this function, as claimed, must drop all the sub-objects within the `HeavyObject` type, then those objects must have been copied from the original object.

If you instead define the function to take in a reference (by adding just two `&` characters into your program), the single-threaded case is now almost 100x faster than the multithreaded case.

Here's a link to a Rust Playground with just those two characters changed: https://play.rust-lang.org/?version=stable&mode=debug&editio...

Note that the code that drops the data in a separate thread is not timing the amount of time your CPU is spinning, dropping the data. So while this does decrease the latency of the original thread, the best solution is to avoid copying and then freeing large, complex objects as much as possible. While it is of course necessary to do this sometimes, this particular example is just not one of them. :)

As an aside, I'm somewhat surprised that the Rust compiler isn't inlining and eliminating all the copying and dropping; this would seem to be a classic case where compiler analysis should be able to determine that `a.size()` should be computable without copying `a`, and it should be able to eliminate the function call cost as well. Manually doing this gives the exact same timing as my gist above, so I assume that this is happening when passing a reference, but not happening when passing the object itself.

staticfloat··on MacOS Catalina: Slow by Design?
If you read the comments of the article and do your own testing, you will find that reality appears to be more complicated than the article suggests. Users have shown using both timing and wireshark that the shell scripts do not appear to be triggering notarization checks.
staticfloat··on Neurable Technology
Wow, I did my undergrad EE with Ramses at UW! Good to see his company seems to be doing well!
staticfloat··on Memory Bandwidth Napkin Math
Sorry, I don't mean for this to come across as playing down your post; you're right that "profoundness" is completely subjective and there's no use in me saying "it's not that profound", for that I apologize.

I enjoyed your post quite a bit, especially how far you went in order to get concrete results that back up the theory. Thank you for going through the effort of writing this post and educating others on your findings. :)

staticfloat··on Memory Bandwidth Napkin Math
I actually don't think this is that profound; when OP is testing datasets that fit within cache, what's happening is that we are simply not waiting for data to be loaded in from RAM. Let's look at this from a different angle; instead of looking at GB/s, let's think about the CPU as a machine that executes instructions as fast as it can, then look at what can go wrong.

I could write a program in assembly that is simply 1000000 triplets of (load, add, store) instructions, each reading from a sequentially-increasing memory location. We could think of it like a fully-unrolled addition loop. My CPU, operating at 3GHz, supposedly should complete this program in ~1ms (3 million instructions running at 3 billion instructions per second), but (spoiler alert) it doesn't. Why?

The answer is that the CPU executes instructions as quickly as it can, but it spends an awful lot of time waiting around for various subsystems to finish doing something. Unless you are running highly optimized code, chances are your CPU, even though "utilization" is pegged at 100%, has more circuits sitting around doing nothing than it has circuits doing something. One of the largest contributors to your CPU impatiently tapping its foot is the memory subsystem, because fetches from RAM are so slow, and the `load` instruction (and all dependent instructions such as the `add` and `store` instructions) is completely blocked upon that instruction finishing completely.

To help with this, we have the whole cache hierarchy, with prefetching of whole cache lines and whatnot, to try and grab pieces of memory that we think will be used next into cache, such that we can then access them with _much_ lower latency (and therefore higher bandwidth, since the only thing preventing us from processing more data is waiting around for more data to come in, ergo latency is directly the inverse of bandwidth in this case).

Therefore, when doing random accesses upon a dataset that won't fit into cache, we expect the average time-per-instruction-retired to be roughly how long it takes to pull it in from RAM. Sequential access is faster only because when I ask for a value and it doesn't exist, I grab not only that whole value, but the entire cache line at once, such that the next couple of loads only have to reach out to cache. Smart compilers can place prefetching hints in here as well, so that future loads are overlapping our accesses to cache.

The reason I find random access into cache having the same performance as sequential access as not that profound is because it falls out directly from the above scenario: sequential access into RAM _is_ random access of cache! The reason sequential access to RAM is fast is because the values are in cache due to having fetched an entire cache line; therefore randomly accessing those same cache buckets in a random order is equivalent (from this perspective).

For those that are interested in learning more about the cache hierarchy and why it's important to organize your algorithms/datastructures such that you can do a lot of work without your working set falling out of cache, I highly recommend reading the HPC community's tutorials on writing high-performance matrix multiplication. Writing a fast GEMM is one of the simplest algorithms and datastructures that will drive home why we have tricks like tiling, loop unrolling, loop fusion, parallelization, etc.. to make maximal use of the hardware available to us.

For those that like academic papers, Goto et. al's paper has a lot of this laid out nicely: https://www.cs.utexas.edu/~flame/pubs/GotoTOMS_revision.pdf For those that like follow-along tutorials, this was a fun one for me: http://apfel.mathematik.uni-ulm.de/~lehn/sghpc/gemm/

staticfloat··on Ken Thompson's Unix Password
My highschool (well, homeschool resource center) IT admin couldn't log into one of the macs in the A/V lab one day; I heard him talking about it, and being on good terms with him, I offered to try and hack in. I literally googled "how to hack macos password", chanced upon an `nidump` vulnerability recent enough that it hadn't been patched, used that to dump the password hash file, fed that to JTR (compiled on that same machine, to add insult to injury), and almost instantly ended up with the admin password for the entire domain: 1337

It turned out that someone hadn't changed the password, he had just mistyped it over and over again. At the time, I didn't know what "1337" meant, I just thought it was a weird number, and it wasn't until many years later that I suddenly burst into laughter, realizing the "elite" level of security in that lab.

Thanks for the good times, Ron! I'm really glad he just laughed and trusted me as I explored technology instead of freaking out when my portscanners started making the printer spew out a bunch of garbage.

staticfloat··on Giving up on Julia (2016)
As one of the Julia developers; this is quite atypical. We’d like to get a bug report on our GitHub tracker from you if you’re willing to open one. Anecdotally, on my 2018 MacBook Pro, full startup of Julia, compilation and execution of a syntax error, and cleaning everything up, takes about 0.8s. (Measured with “time julia -e ‘foo foo foo’”). That’s not a time to brag about, but it’s an order of magnitude faster than your comment. Your system may be slower about certain things, but tens of seconds is way far out of the distribution of reasonable times.

Creating an array of three numbers is much faster; on my system (subtracting startup time) it’s less than 50ms, and that’s all because of compilation time. After running it once (so as to compile the random number generation and array construction routines) constructing a random array takes ~4ns.

Again, we’d like to see an issue opened in our github tracker to help figure out whats going wrong. Feel free to open one at https://github.com/JuliaLang/julia

staticfloat··on Giving up on Julia (2016)
Be sure to read the sibling comment from ddragon; it’s not necessary to explicitly write type annotations, that should never increase performance. It only alters when a particular function can be called. The “secret sauce” in Julia is the aggressive type inference that gets run on non-typed code, determining the types (when possible) of your entire program.
staticfloat··on Giving up on Julia (2016)
The implicit assumption in much of the Julia world is that the work being done on the dataset will dwarf the startup time; if your workload takes less than 100ms, it’s probably not worth spending tens of milliseconds on compiling your code in the first place, and you should run it with an interpreter. (Incidentally Julia does support that, but it requires a command-line flag, and can come with a heavy performance penalty if you are running numérica-heavy code)
staticfloat··on Exploring the Visible Spectrum in Python
This mapping of wavelength to RGB value is very misleading: in particular, when moving to higher and higher frequencies, blue does not wrap around to red creating purple. The perceptual color space that we are used to working in by mixing pigments or various frequencies of light is independent of light frequency. Unlike all other colors in the rainbow, there is no single frequency that will activate both the red and blue cones within your eye to create purple; if you had a single frequency between blue and red, it would look green. Instead, you must have two disjoint frequencies both stimulating the red and blue cones simultaneously (note that this is not strictly true, as for some people, the red cones do have some response down in the blue range, but the amount of red response within the blue region is extremely weak, and will not give the sensation of purple that is shown in articles such as this one).

This is why rainbows do not have purple at the edge, and why prisms do not create purple; it is a "non-physical" color (I made that term up, but it hopefully makes sense given my explanation above).

For a more physically-sound mapping of wavelength to color, see the CIE XYZ color system [1]. In particular, in Figure 2, the "pure wavelengths" are given as points along the outside edge of the curved surface, and as you can see, purple does not exist along that outside edge.

For those interested in simulation of color, I highly recommend Jiahao Chen's old notebook "The Colors of Chemistry" [2], which goes through a lot of these concepts, and accurately simulates things like the color of solutions just from knowing their chemical composition. Truly fascinating stuff.

[1] http://www.fourmilab.ch/documents/specrend/ [2] https://github.com/jiahao/ijulia-notebooks/blob/master/2014-...

staticfloat··on Composable multi-threaded parallelism in Julia
Thanks! We do our best to make sure that people who want to get ahold of the latest Julia binaries are always able to. Our downloads page [0] always offers the latest stable builds, whereas the nightlies page [1] allows you to download binaries off of the tip of the `master` branch, which usually lag behind the github repository by only a couple of hours.

[0] https://julialang.org/downloads/ [1] https://julialang.org/downloads/nightlies.html

staticfloat··on Show HN: Roc – Real-Time streaming over the network
I once wanted to do something similar, so I wrote my own realtime audio streaming tool [0]; the UX resembles something more akin to a foot-gun than the polished (and much more feature-complete) Roc, but it does have excellent latency properties by running at the absolute ragged edge of what it can.

You could connect multiple clients to stream music to a single server (or multiple servers if you wanted to); the server kept a list of pending audio buffers for each client, mixing them all together into a ring buffer that gets spat out to portaudio. If a client underran, it simply missed that ring buffer rotation, and it "fell behind" by one buffer length (we request the minimum latency from portaudio, so this is usually measured in single-digit milliseconds). That would cause a few crackles and pops in the first second or two as the natural jitter of the network caused the client to underrun a few times, but then it would stabilize. (This didn't bother me much, as I usually was playing silence when I first connected it in any case).

In my experiments, the overall system latency was pretty close to the perceptual limit, I would estimate around 10ms, streaming over wifi from my laptop to a raspberry pi.

[0] https://github.com/staticfloat/popuset

staticfloat··on SpaceX Gets FCC Approval to Sell Wireless High-Speed Home Internet from Space
You do not need a high-end FPGA; you need a bunch of small antennas (potentially extremely cheap, as these kinds of antennas are often microstrip antennae etched into PCBs), and a bunch of microwave-rated components such as mixers, phase-shifters and capacitors. The phase shifters allow you to add up the signals in such a way as to form a coherent beam in a certain direction, this is known as beamforming [0], and with some admittedly complex precalculation, an electronically-steerable antenna array needs nothing more than a simple microcontroller to set the phase shifters for the desired array orientation. Of course you would want to adaptively steer it based on received signal, but this can be done without the need for fancy FPGAs; your receiver signal chain will include all the mixers, demodulators and signal processing elements needed already baked in silicon; similar to how a WiFi chipset does channel adaptation by analyzing the received signal without resorting to high-end hardware, the kind of system that SpaceX is building does not require particularly exotic hardware (although it would be custom, and expensive to develop).

Speaking of which, there's a neat project that sneaks into a particular Intel chipset and extracts the CSI (Channel State Information) from the wifi hardware so you can actually do things like estimate which frequencies are being attenuated [1]. It's out of date, but still shows the kind of live, complex, signal analysis that is possible with commodity-priced hardware.

In short, I think it is certain that a base station can be built for <$1000, and likely that it can be built for <$150, although without knowing the precise specifications, it's hard to say. We did a case study on this in my antennas course in grad school 3-4 years ago, and our ending numbers on our prototype designs (not fully built and tested, of course, but simulated and spec'ed out with real-world pricing) were around $150. I am certain that our design as it was would not work, but something similar to it likely would. ;)

[0] https://en.wikipedia.org/wiki/Beamforming [1] https://dhalperi.github.io/linux-80211n-csitool/

staticfloat··on 5G Is Likely to Put Weather Forecasting at Risk
While bandwidth is typically measured in absolute Hz difference due to the spread directly relating to the amount of information-carrying capacity, when you are actually generating signals many things scale with carrier frequency.

The way most of these signals are being generated is through an "information signal" (usually referred to as a modulator) being created within your device, then moved up in frequency by combining it with a "carrier signal". This separation allows the information signal's properties (such as bandwidth, modulation scheme, bitrate/Hz, etc...) to be more-or-less independent from the physical characteristics of propagation, which will be more strongly related to the carrier signal's properties (e.g. water absorption, reflection/transmission off of/through materials, etc...).

However, no process is perfect, and although we would like to generate perfect signals, we can't. Distortion appears in multiple parts of this pipeline, both in the generation of the low-frequency modulator and the high-frequency carrier. Distortion typically comes from "linear" components not being perfectly linear and thereby generating harmonics of the signal passing through them. In the case of the modulator, this splatters signal energy up and down the spectrum on the order of the bandwidth of the modulator, but in the case of the carrier it does so on the order of the carrier frequency. This is all a matter of degree and depending on the application may not be that big a deal, but it definitely must be addressed for high-density communications equipment like cell networks.

All transmitters have filters at multiple stages of their signal processing chains, both lowpass filters that filter the modulator before it's mixed with the carrier signal to boost it up in frequency, as well as bandpass filters that ensure the output stays within the bounds it's meant to be; but these filters only do so much and they can be expensive to create (as they can have rather fine physical tolerances) so everybody is always just playing the "do the best job we can for the least money" game.

Luckily, most transmitters are also connected to antennae that provide a convenient filtering on the output (they only resonate at the frequencies they transmit at) which helps for specialized systems that only operate at a single transmit frequency, but for something like 5G is less helpful, due to the many channels it supports, causing the antenna to necessarily support a wide range of frequencies.

staticfloat··on Ginseng: Keeping secrets in registers when you distrust the operating system
The second sentence of the article is:

    It arranges things such that this sensitive data is only ever in the clear in registers (never memory),
    and is saved in an encrypted memory region called the secure stack on context switches
staticfloat··on RaptorQ and performance optimization in Rust
It vastly reduces the chance of you receiving a packet that contains no new information. If you just rebroadcast the same packets over and over again, a receiver that has 999/1000 packets may have a very low chance of serendipitously receiving the packet they need.
staticfloat··on 'Big Data' Has Come to Mean 'Small Sampled Data'
Independent of the usage within the article, I make this kind comparison pretty often when thinking about how to "saturate" a device; if I am building a specialized system to perform a task, I want the capability of the system in terms of Hz per byte of storage to roughly match that of the problem being performed. (This is typically measured against storage bandwidth, not storage total, since you usually have the assumption that you're bringing data in from a larger storage pool that is essentially "infinite" in comparison (cache <---> main memory, or main memory <---> disk, or disk <----> distant server))

If your Hz/byte ratio is too high. you're wasting compute capability. If too low, you're wasting memory/memory bandwidth.

staticfloat··on How AI Training Scales
Yes, we're saying the same thing. It takes longer for the optimizer to converge when using larger batch sizes, in terms of number of samples pushed through the model. It takes less time in terms of wall clock time due to increased efficiency.
staticfloat··on Noah: Bash on Ubuntu on macOS
I'm quite interested in Darling, mostly for the purpose of cross-compiling from Linux -> Darwin. We cross-compile a lot of things for Julia, however cross-compiling executables that need to bootstrap something, (e.g. building something that then needs to run to generate output which is then used in further builds) can often get tricky. There are of course workarounds that can be utilized in order to get this to work (build a Linux-native version and a Darwin version, then use the Linux-native version of the software whenever a bootstrap is required) but this is tedious and not foolproof.
staticfloat··on How AI Training Scales
batchsize 1 should always work. The trade off is always “bigger batch size/more efficient” versus “smaller batch size/better results”. By using larger batches you are able to reduce overhead, take better advantage of memory caches, etc.... which can give very large speed ups. But mathematically it can slow down your training convergence (check out the plots showcasing how smaller batches converge in fewer optimizer steps). This is because when you average over an entire batch, you throw away a little bit of information that could otherwise be used to better learn your model. For intuition on this, imagine that you are doing homework, and you are forced to finish 1000 practice problems before being allowed to look at the answers; if you had looked earlier you may have been able to correct your mistake earlier and not gotten all 1000 wrong. However it would have taken you more time to go through those 1000 practice problems because you would have been looking up the answers and mentally adjusting your model for each problem you did.
← PreviousPage 2 of 3Next →