How to access GPIO pins on Atmega with memory mapping?
Is there a good way to solve this with virtually no overhead?


There is no practical way to get 'virtually no overhead' in C on AVR. Code that looks like it should have low overhead often doesn't, and you have to look at the disassembly to see the real code.

AVR has a RISC architecture that needs many instructions to do 'simple' operations. IN, OUT, and SBI/CBI (bit set/clear) instructions take literal arguments only, which must be 'hard-coded' at compile time. But there is no barrel shifter, so bit masks computed at run time can take many instructions to produce. Pointers are made from 8 bit registers that are loaded one byte at a time, so pointer operations may use more cycles than port I/O with conditional code.

Here's the best I have managed to produce so far (It's basically the same as your code except the pin masks are precomputed, and it's wrapped in a function instead of inlined)... 

// LED pin masks
#define LP0  1&lt;&lt;0
#define LP1  1&lt;&lt;1
#define LP2  1&lt;&lt;2
#define LP3  1&lt;&lt;3
#define LP4  1&lt;&lt;4
#define LP5  1&lt;&lt;5
#define LP6  1&lt;&lt;6
#define LP7  1&lt;&lt;7
#define LP8  1&lt;&lt;0
#define LP9  1&lt;&lt;1
#define LP10 1&lt;&lt;2
#define LP11 1&lt;&lt;3
#define LP12 1&lt;&lt;4
#define LP13 1&lt;&lt;5
#define LP14 1&lt;&lt;0
#define LP15 1&lt;&lt;1

// pin mask array
uint8_t ledpin_mask[] = {LP0,LP1,LP2,LP3,LP4,LP5,LP6,LP7,LP8,LP9,LP10,LP11,LP12,LP13,LP14,LP15};

// turn on LED
void setled(uint8_t pin)
{
  if (pin &lt; 8)
  {
       PORTD |= (ledpin_mask[pin]);
  }
  else if ( pin &lt; 14)
  {
       PORTB |= (ledpin_mask[pin]);
  }
  else
  {
       PORTC |= (ledpin_mask[pin]);
  }
}


...which generates the following machine code:-

void setled(uint8_t pin)
{
  96:   28 2f           mov r18, r24
  98:   30 e0           ldi r19, 0x00   ; 0
if (pin &lt; 8)
  9a:   88 30           cpi r24, 0x08   ; 8
  9c:   40 f4           brcc    .+16        ; 0xae &lt;setled+0x18&gt;
{
     PORTD |= (ledpin[pin]);
  9e:   9b b1           in  r25, 0x0b   ; 11
  a0:   f9 01           movw    r30, r18
  a2:   e0 50           subi    r30, 0x00   ; 0
  a4:   ff 4f           sbci    r31, 0xFF   ; 255
  a6:   80 81           ld  r24, Z
  a8:   89 2b           or  r24, r25
  aa:   8b b9           out 0x0b, r24   ; 11
  ac:   08 95           ret
}
else if ( pin &lt; 14)
  ae:   8e 30           cpi r24, 0x0E   ; 14
  b0:   40 f4           brcc    .+16        ; 0xc2 &lt;setled+0x2c&gt;
{
     PORTB |= (ledpin[pin]);
  b2:   95 b1           in  r25, 0x05   ; 5
  b4:   f9 01           movw    r30, r18
  b6:   e0 50           subi    r30, 0x00   ; 0
  b8:   ff 4f           sbci    r31, 0xFF   ; 255
  ba:   80 81           ld  r24, Z
  bc:   89 2b           or  r24, r25
  be:   85 b9           out 0x05, r24   ; 5
  c0:   08 95           ret
}
else
{
     PORTC |= (ledpin[pin]);
  c2:   98 b1           in  r25, 0x08   ; 8
  c4:   f9 01           movw    r30, r18
  c6:   e0 50           subi    r30, 0x00   ; 0
  c8:   ff 4f           sbci    r31, 0xFF   ; 255
  ca:   80 81           ld  r24, Z
  cc:   89 2b           or  r24, r25
  ce:   88 b9           out 0x08, r24   ; 8
  d0:   08 95           ret


That's 12 or 14 machine code instructions executed depending on the port, which is probably about as close as you can get to 'virtually no overhead' without coding in optimized assembler.