art with code

2011-02-05

Trying to make sense of this whole cooling thing

I kind of find CPU heat sinks interesting. Probably because of the noisy fans messing with my concentration. Why do we need those things anyhow?

I'm not a physicist so if I'm saying Very Silly Things below, I'd appreciate if you left a comment to set me straight.

Fans are devices to move ambient-temperature coolant fluid (read: air) into contact with heat sink fins, so as to maximize heat conduction from the heat sink to the environment. Heat conduction = thermal conductivity * area * temperature difference / distance between the different temperatures [according to Wikipedia]. If you think of the heat sink surface–ambient air -system in terms of that model, a fan would be trying to minimize the distance between the hot heat sink and the cool air. In practice, pushing off the boundary layer of hot air and replacing it with cooler air.

There are also other methods for optimizing the heat conduction equation, here's a small roundup. Endothermic reactions for soaking up heat and transporting it away: heat pipes. Maximize the temperature difference: dry ice, LN2 (plus the boiling is endothermic). Increase thermal conductivity of the coolant fluid: water cooling, mineral oil cooling. Increase conducting area: finned heat sinks, larger heat sinks - and I guess the increased mass acts as a thermal buffer that soaks up spikes in heat production. Decrease distance between the different temperatures: fan pushing in cold air, chimney enhancing natural convection. (Natural convection: hot air expands, making it less dense than cool air, which lets cool air fall below hot air. As long as you have gravity, that is.)

Sci-fi, where our Author reveals his lack of understanding in The Art of Physicks

  • You could also get cold air close to the heat sink fins by ionizing air and charging the heat sink with reverse polarity to attract the cool air to the heat sink (hopefully neutralizing the air in the process). Maybe that could get cold air more effectively through the boundary layer than mechanical pushing with a fan? They're doing some commercial research into that, and there's also a DIY Ion Cooler.

  • Some sort of channels on the heat sink surface to require less airflow to replace the boundary layer. Funnel the air to a higher velocity to push through the boundary layer more efficiently.

  • This is neat: vibrating piezoelectric heat sink fins.

  • Heat travels as phonons, maybe you could somehow create a heat waveform and sap it out through destructive interference with an audio source. Sort of like noise cancelling headphones. Audible noise is in the kilohertz range, heat is in the gigahertz range. Apparently these guys at MIT are making phononic mirrors.

  • Make the heat do work, cooling the heat sink down in the process. Stuff a heat sink full of piezoelectric crystals to convert the thermal expansion of the heat sink into electricity, conduct the electricity away to cool the heat sink.

  • Can heat be focused? Heat up a small region of the heat sink to a high enough temperature for blackbody radiation to really kick in, use mirrors to transport the resulting photons away. (Intensity of blackbody radiation grows as fourth power of absolute temperature, temperature grows roughly linearly with power input (apart from phase changes).

  • A meter-high 0.1x0.1m chimney with 50C internal air and 20C room temperature could generate 0.01 m^3/s airflow, or 22 CFM? Plugging in values to Q = C*A*sqrt(2*g*H*(Ti/T0-1)): 0.7 * 0.01m^2 * sqrt(2*9.81m/s^2*1m*(323K/293K-1)) = 0.0099 m^3/s.

  • Rotate the chimney, get a fire tornado!

  • An interesting page on thermal design

2011-01-27

Intel X25-V SSD

Bought an Intel X25-V 40GB SSD to do some random read benchmarking. Don't stick it into a PATA controller driven SATA port, as that limits the bandwidth to 70 MB/s and random access times to 5 ms.

Sequential read speed on the unused disk was limited by the SATA bus. So I guess it's not actually reading anything for unused blocks, just generating a string of zeroes on the controller and sending it back. Would be pretty amusing to have a special disk driver that keeps a list of the unused blocks in system RAM and generates the data on the CPU for any read accesses to them. You could probably keep it all in L1 and get nice bogus 4k random read benchmark numbers. "1 ns access latency!?? 100 GB/s random read bandwidth??? Whaaaat?" (An allocation bitfield for 80 gigs in 512k blocks is about 20 kB. Increase blocksize or add RLE for bigger volumes.)

Read speed on actual data was around 200 MB/s. Average random read time for a 4k block was 0.038 ms with 128 threads and 0.31 ms with a single thread. So the controller can do 8 reads in parallel. Which is nice for a cheapo SSD.

Didn't really test write performance apart than doing a cursory check. And yes, it is low bandwidth. 40 MB/s streaming writes. So it's best used as a random read drive.

Might make a nice random read array with 6 drives or somesuch. Hypothetically: 48 parallel reads, 160 000 4k reads per second (650 MB/s). And then all you need is software that can take advantage of that.

Getting flash chip latencies lower would be good.

2011-01-15

How to take advantage of that memory?

[edit] Here's an rc.d init script I'm using to prefetch the FS to page cache: usr-prefetch. Copy to /etc/init.d and run `update-rc.d usr-prefetch start 99 2 .` to add it to runlevel 2 init scripts. I currently have 4GB of RAM, and the total size of the prefetched stuff is 3.7GB, so it might actually help. Maybe I should go buy an extra 2GB stick. It takes about a minute to do the prefetch run with cold cache, so the average read speed is around 60 MB/s. Which is pretty crappy compared to the 350 MB/s streaming read speed, maybe there's some way to make the prefetch a streaming read.

Ubuntu's ureadahead reads in the files accessed during boot in disk order. If you add all your OS files to it, it should be possible to stream the whole root filesystem to page cache in about ten seconds. I dunno.

And now I'm reading through ureadahead's sources to figure out how it sorts the files in disk order. It's using fiemap ioctl to get the physical extents for the file inodes, then sorts the files according to the first physical block used by the file. To read the files to the page cache, it uses readahead. If there was some way to readahead physical blocks instead of files, you could collapse the physical blocks into spans with > x percent occupancy, do a single streaming read per span (and cache only the blocks belonging to the files to cache).

Another easy way to do fast caching would be to put all system files on a separate partition, then cache the entire partition with a streaming read. Or allocate a ramdisk, dd the root fs onto it at boot, use it through UnionFS with writes redirected to the non-volatile fs. But whether that's worth the bother is another thing.
[/edit]

In the previous post I put together a hypothetical machine with 16GB RAM and 1.5GB/s random access IO bandwidth. Which is total overkill for applications that were built for machines with 100x less resources. What is one to do? Is this expensive hardware a complete waste of money?

The first idea I had on taking advantage of that amount of RAM was to preload the entire OS disk to page cache. If the OS+apps are around 10GB in total, it'll take less than 10s to load them up at 1.5GB/s. And you'll still have 6GB unused RAM left, so the page cache isn't going to get pushed out too easily. With 10s boot time, you'd effectively get a 20GB/s file system 20 seconds after pushing the power button.

Once you start tooling around with actual data (such as video files), that is going to push OS files out of page cache, which may not be nice. But what can you do? Set some sort of caching policy? Only cache libs and exes, use the 1.5GB/s slow path for data files? Dunno.

If you add spinning disks to the memory hierarchy, you could do something ZFS-like and have a two-level cache hierarchy for that there filesystem. 10GB L1 in system RAM, 240GB L2 on SSDs, a couple TB on spinning disks. But again, how sensible is it to cache large media files (which will likely be the primary use of the spinning disks.) IT IS A MYSTERY AARGH

It'd also be nice to have a 1-2GB of faster RAM between the CPU caches and the system RAM (with GPU-style 150GB/s bandwidth), but maybe you can only have such things if you have a large number of cores and don't care about latency.

But hmm, the human tolerance for latency is around 0.2s for discrete stuff, 0.015-0.03s for continuous movement. In 0.2s you can do 4GB worth of memory reads and 0.3GB of IO, in 0.02s the numbers are 0.4GB and 0.03GB respectively. On a 3.5 GHz quad-core CPU, you can execute around 3 billion instructions in 0.2s, which'd give you a ratio of 0.75 ops per byte for scalar stuff and 6 ops per byte for 8 op vector instructions. On GPUs the ops/byte ratio is larger, at close to 20 ops/byte for vector ops, which makes them require more computation-heavy algorithms than CPUs.

In 4GB you can fit 60 thousand uncompressed 128x128 images with some metadata. Or hmm, 6 thousand if each has ten animation frames.

2010-12-19

WebGL Orrery


I wrote a small WebGL orrery a month back or so, using planet and orbit data from Wikipedia. The orbits came out a bit wrong (planet positions on orbits are wrong, the planet rotation axis rotates along the orbit, planets velocities are constant, the orbits / axes may well be flipped somewhere), but it's a passably "realistic" orrery. Well, realistic apart from the whole having to scale down orbits by a factor of thousand to make the planets visible -thing. With 1:1 orbits, the planets are smaller than a pixel in size when viewed from other planets and the sun is about a pixel in diameter when viewed from Neptune.

It's quite extensible too, it should be possible to add in all the moons just by filling in their data. The coordinate system and z-buffer aren't accurate enough to do anything fancy like a spaceship sim with 1:1 orbits though :)

2010-12-01

A deck of cards

If not for the order property, you could cram a deck of cards into a 64-bit int. Though if you had a RNG cyclical in full_deck_size, that'd work to preserve the order property. Which would be enough for poker and other stackless or write-only stack card games.

With the cyclical RNG you could pop a card off a full deck in O(1). With incomplete decks (that is, a deck with non-RNG-generated cards removed) you'd need to go through random cards until the generated card is in the deck. The worst-case time-complexity for popping all cards off an incomplete deck would be O(full_deck_size) and the average complexity for popping one card off an incomplete deck would be O(full_deck_size/incomplete_deck_size). Anyhow, incomplete decks would be as fast or slow as full decks, with an extra existence check slowing them down a bit further. The RNG would need a seed with log2(fac(full_deck_size)) bits, so log2(52!) = 226 bits for a deck without jokers.

A naive version of the RNG would be to generate a random number between 0 and 52!-1, then convert the number into 52 digits in factorial base. Divide first by 51!, the result is the first card. Divide the remainder by 50!, the result is the second card (note that you have to remove the first card from consideration for the second card index). Continue dividing the remainders until you have 52 cards. Or just generate a shuffled 52 element array of numbers and go through that :P

... I should get back to work now.

Source

#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

typedef int64_t card_mask;
typedef int8_t card;

typedef struct deck {
card cards[55];
int8_t length;
card_mask mask;
} deck_t;

#define suit_size 13

static const card_mask hearts = suit_size*0;
static const card_mask spades = suit_size*1;
static const card_mask clubs = suit_size*2;
static const card_mask diamonds = suit_size*3;
static const card_mask jokers = suit_size*4;

static const char* suits[5] = {"Hearts", "Spades", "Clubs", "Diamonds", "Jokers"};
static const char* ranks[13] = {
"Ace", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten",
"Jack", "Queen", "King"
};

card_mask remove_cards_from_mask(card_mask deck, card_mask cards)
{
return deck & ~cards;
}

card_mask add_cards_to_mask(card_mask deck, card_mask cards)
{
return deck | cards;
}

card_mask query_mask(card_mask deck, card_mask cards)
{
return deck & cards;
}

card_mask mask_of_card(card c)
{
return (card_mask)1 << (card_mask)c;
}

card card_for_suit_and_rank(card_mask suit, card_mask rank)
{
return suit + rank;
}

int deck_initialize(deck_t *deck, int jokers)
{
card c = 0;
if (jokers < 0 || jokers > 3)
return 0;
deck->length = 52 + jokers;
deck->mask = ((card_mask)1 << (card_mask)deck->length)-1;
for (c=0; c<deck->length; c++) {
deck->cards[c] = c;
}
return 1;
}

void deck_clear(deck_t *deck)
{
deck->length = 0;
deck->mask = 0;
}

void deck_shuffle(deck_t *deck)
{
int8_t i,j;
card tmp;
for (i=deck->length-1; i>0; i--) {
j = rand() % (i+1);
tmp = deck->cards[j];
deck->cards[j] = deck->cards[i];
deck->cards[i] = tmp;
}
}

card_mask deck_query_mask(deck_t *deck, card_mask m)
{
return query_mask(deck->mask, m);
}

card_mask deck_query_card(deck_t *deck, card c)
{
return deck_query_mask(deck, mask_of_card(c));
}

card_mask deck_query_deck(deck_t *deck, deck_t *deck2)
{
return deck_query_mask(deck, deck2->mask);
}

int deck_add(deck_t *deck, card c)
{
card_mask m = mask_of_card(c);
if (query_mask(deck->mask, m)) {
return 0;
} else {
deck->mask = add_cards_to_mask(deck->mask, m);
deck->cards[deck->length] = c;
deck->length++;
return 1;
}
}

int deck_remove(deck_t *deck, card c)
{
int8_t i=0, j=0;
card_mask m = mask_of_card(c);
if (!query_mask(deck->mask, m)) {
return 0;
} else {
deck->mask = remove_cards_from_mask(deck->mask, m);
for (i=0,j=0; i<deck->length; i++) {
if (deck->cards[i] != c) {
deck->cards[j] = deck->cards[i];
j++;
}
}
deck->length = j;
return 1;
}
}

card deck_pop(deck_t *deck)
{
card c;
if (deck->length < 1) {
return -1;
} else {
deck->length--;
c = deck->cards[deck->length];
deck->mask = remove_cards_from_mask(deck->mask, mask_of_card(c));
return c;
}
}

const char* suit_string(card s)
{
if (s >= suit_size*5)
return "Invalid";
return suits[s / suit_size];
}

const char* rank_string(card s)
{
return ranks[s % suit_size];
}

void print_card(card c)
{
printf("%s of %s\n", rank_string(c), suit_string(c));
}

void print_deck(deck_t *deck)
{
int8_t i;
printf("Deck %p:\n", (void*)deck);
for (i=0; i<deck->length; i++) {
print_card(deck->cards[i]);
}
}

void print_mask(card_mask m)
{
int i;
const char* symbols[5] = {"♥", "♠", "♣", "♦", "☻"};
for (i=0; i<64; i++) {
if (i%suit_size == 0) printf(" %s", symbols[i/suit_size]);
if ((m>>i) & 1) putchar('#');
else putchar('.');
}
putchar('\n');
}

int main()
{
int i=0;
deck_t deck, hand;
card c;
srand(time(NULL));
deck_initialize(&deck,2);
deck_initialize(&hand,0);
deck_clear(&hand);
deck_shuffle(&deck);
printf("Deck %p has %d cards and mask %ld.\n", (void*)&deck, deck.length, deck.mask);
print_mask(deck.mask);
for (c=0; c<54; c++) {
if (!deck_query_card(&deck, c)) {
printf("Error: Deck doesn't have a card.\n");
print_card(c);
}
}
printf("Hand %p has %d cards and mask %ld.\n", (void*)&hand, hand.length, hand.mask);
print_mask(hand.mask);
for (c=0; c<54; c++) {
if (deck_query_card(&hand, c)) {
printf("Error: Hand has a card it shouldn't have.\n");
print_card(c);
}
}
deck_remove(&deck, hearts);
if (deck_query_card(&hand, hearts))
{ printf("Error: Hand has a card it shouldn't have.\n"); print_card(hearts); }
deck_remove(&deck, spades);
if (deck_query_card(&hand, spades))
{ printf("Error: Hand has a card it shouldn't have.\n"); print_card(spades); }
deck_remove(&deck, clubs);
if (deck_query_card(&hand, clubs))
{ printf("Error: Hand has a card it shouldn't have.\n"); print_card(clubs); }
deck_remove(&deck, diamonds);
if (deck_query_card(&hand, diamonds))
{ printf("Error: Hand has a card it shouldn't have.\n"); print_card(diamonds); }
printf("Deck %p has %d cards and mask %ld.\n", (void*)&deck, deck.length, deck.mask);
print_mask(deck.mask);
for (i=0; i<5; i++) {
c = deck_pop(&deck);
print_card(c);
deck_add(&hand, c);
}
printf("Deck %p has %d cards and mask %ld.\n", (void*)&deck, deck.length, deck.mask);
print_mask(deck.mask);
printf("Hand %p has %d cards and mask %ld.\n", (void*)&hand, hand.length, hand.mask);
print_mask(hand.mask);
print_deck(&hand);
if (deck_query_deck(&deck, &hand) || deck_query_deck(&hand, &deck)) {
printf("Error: Deck and Hand intersect.\n");
} else {
printf("Deck and Hand are disjoint.\n");
}
for (i=0; i<hand.length; i++) {
if (deck_query_card(&deck, hand.cards[i])) {
printf("ERROR ");
} else {
printf("OK ");
}
}
printf("\n");
for (i=0; i<deck.length; i++) {
if (deck_query_card(&hand, deck.cards[i])) {
printf("Error: Found a card in Deck that is in Hand.\n");
print_card(deck.cards[i]);
}
}
print_card(suit_size*5);
return 0;
}

Output

$ ./a.out
Deck 0x7fff98907250 has 54 cards and mask 18014398509481983.
♥############# ♠############# ♣############# ♦############# ☻##..........
Hand 0x7fff98907210 has 0 cards and mask 0.
♥............. ♠............. ♣............. ♦............. ☻............
Deck 0x7fff98907250 has 50 cards and mask 18013848686551038.
♥.############ ♠.############ ♣.############ ♦.############ ☻##..........
Eight of Hearts
Seven of Clubs
Ten of Diamonds
Six of Diamonds
Nine of Hearts
Deck 0x7fff98907250 has 45 cards and mask 17714777228828286.
♥.######..#### ♠.############ ♣.#####.###### ♦.####.###.### ☻##..........
Hand 0x7fff98907210 has 5 cards and mask 299071457722752.
♥.......##.... ♠............. ♣......#...... ♦.....#...#... ☻............
Deck 0x7fff98907210:
Eight of Hearts
Seven of Clubs
Ten of Diamonds
Six of Diamonds
Nine of Hearts
Deck and Hand are disjoint.
OK OK OK OK OK
Ace of Invalid

2010-11-23

Yet more GREAT ideas

Amateur astronomer telescopes, live video streaming, combine the different observations into high-res frames: live 24/7 HD webcams of the planets.

Speckle imaging
Compressed sensing
Image Deblurring with Blurred/Noisy Image Pairs

More GREAT ideas

Take The European Citizens' Initiative, add a public forum, link it up with EU proceedings, build a Citizens' Parliament.

The ECI proposal says that you need a million signatories to send a citizens' initiative to the European Commission. The EC is the main proposal-generating entity in the EU. The European Parliament, in contrast, is the proposal-filtering entity (now with new proposal-generating powers granted to it by the Lisbon Treaty). In that sense, the ECI impacts the very top of the EU legislative system.

The million signatories must include 0.2% of the voting-age citizens for a third of the member states to be valid. And after collecting 300 000 statements of support, you must submit it to the EC for an admissibility acid check where the EC figures out whether they can legally do it or not. After two months, you should get back a yay/nay. And you can collect the statements of support online as long as your mechanism is secure (further details pending).

After that, 3 months of checking the signatures for validity by the member states using random sampling. After that, 4 months of examination by the EC, at the end of which it "would then be required to set out its conclusions on the initiative and the action it intends to take in a communication, which would be notified to the organiser as well as to the European Parliament and Council and would be made public."

Not the most blisteringly fast method of democratic participation, is it? Still, it's very interesting.

I wonder how much governance is a problem of thinking power (in terms of "need more heads to think better solutions!") and how much a sensor network problem ("need to know what's wrong with the world to fix it!") and how much a problem of integrating the two into a working system. How would this kind of a wide-ranging massively large parliament work best?

A large and diverse population has experts from all the different walks of life. Which is probably where the thinking power of the citizens' parliament would best manifest. But you need to find the best proposals from the noise of mediocre proposals. Then you have the sensor network side, where a large population is again an asset. But again, there's a large amount of noise with a large population. So perhaps the important thing is making a good network to filter the signal.

Blog Archive