disregard my last comment, `setbyte` doesn't work for obvious reasons. buffer or map-join are the way to go:
```ruby
# encoding: utf-8
require "benchmark"

Encoding.default_internal = Encoding::UTF_8

def index(str)
  str.force_encoding(Encoding::UTF_8).tap do |s|
    unless s.valid_encoding?
      s.each_char.with_index do |c, i|
        s[i] = '?' unless c.valid_encoding?
      end
    end
  end
end

def map_join(str)
  if str.force_encoding(Encoding::UTF_8).valid_encoding?
    str
  else
    str.each_char.map do |c|
      if c.valid_encoding?
        c
      else
        '?'
      end
    end.join
  end
end

def buffer(str)
  if str.force_encoding(Encoding::UTF_8).valid_encoding?
    str
  else
    ''.tap do |buf|
      str.each_char do |c|
        buf.concat(c.valid_encoding? ? c : 63)
      end
    end
  end
end

N = 2000
VALID = "foo£bar£car".encode(Encoding::UTF_8).force_encoding(Encoding::ASCII_8BIT)

puts "VALID: #{VALID.dup}"
puts "index: #{index(VALID.dup)}"
puts "map-join: #{map_join(VALID.dup)}"
puts "buffer: #{buffer(VALID.dup)}"

Benchmark.bmbm do |x|
  x.report('index') { index(VALID.dup * N) }
  x.report('map-join') { map_join(VALID.dup * N) }
  x.report('buffer') { buffer(VALID.dup * N) }
end

INVALID = "£foo\xC0\x8Abar\xC0\x8Acar".force_encoding(Encoding::ASCII_8BIT)

puts "INVALID: #{INVALID.dup}"
puts "index: #{index(INVALID.dup)}"
puts "map-join: #{map_join(INVALID.dup)}"
puts "buffer: #{buffer(INVALID.dup)}"

Benchmark.bmbm do |x|
  x.report('index') { index(INVALID.dup * N) }
  x.report('map-join') { map_join(INVALID.dup * N) }
  x.report('buffer') { buffer(INVALID.dup * N) }
end

```

results: (showing only invalid case for brevity)
MRI 1.9.3:
```
INVALID: £foo�bar�car
index: £foo??bar??car
map-join: £foo??bar??car
buffer: £foo??bar??car
Rehearsal --------------------------------------------
index      0.920000   0.000000   0.920000 (  0.920054)
map-join   0.010000   0.000000   0.010000 (  0.014144)
buffer     0.020000   0.000000   0.020000 (  0.022648)
----------------------------------- total: 0.950000sec

               user     system      total        real
index      0.890000   0.000000   0.890000 (  0.898875)
map-join   0.010000   0.000000   0.010000 (  0.008975)
buffer     0.020000   0.000000   0.020000 (  0.016540)
```

MRI 2.0.0:
```
INVALID: £foo�bar�car
index: £foo??bar??car
map-join: £foo??bar??car
buffer: £foo??bar??car
Rehearsal --------------------------------------------
index      0.810000   0.000000   0.810000 (  0.815064)
map-join   0.010000   0.000000   0.010000 (  0.010653)
buffer     0.020000   0.000000   0.020000 (  0.017207)
----------------------------------- total: 0.840000sec

               user     system      total        real
index      0.770000   0.010000   0.780000 (  0.774781)
map-join   0.010000   0.000000   0.010000 (  0.008739)
buffer     0.020000   0.000000   0.020000 (  0.012607)
```

JRuby 1.7.5:
```
INVALID: £foo�bar�car
index: £foo??bar??car
map-join: £foo??bar??car
buffer: £foo??bar??car
Rehearsal --------------------------------------------
index      1.270000   0.010000   1.280000 (  1.192000)
map-join   0.050000   0.000000   0.050000 (  0.036000)
buffer     0.020000   0.000000   0.020000 (  0.017000)
----------------------------------- total: 1.350000sec

               user     system      total        real
index      1.150000   0.000000   1.150000 (  1.147000)
map-join   0.020000   0.000000   0.020000 (  0.018000)
buffer     0.020000   0.000000   0.020000 (  0.013000)
```

RBX 2.1.1:
```
INVALID: £foo�bar�car
index: £foo??bar??car
map-join: £foo??bar??car
buffer: £foo??bar??car
Rehearsal --------------------------------------------
index      7.347737   0.022950   7.370687 (  7.509184)
map-join   2.457528   0.011470   2.468998 (  2.544675)
buffer     2.469490   0.007916   2.477406 (  2.542838)
---------------------------------- total: 12.317091sec

               user     system      total        real
index      7.394374   0.025542   7.419916 (  7.813794)
map-join   2.404040   0.011272   2.415312 (  2.460207)
buffer     2.457256   0.010527   2.467783 (  2.650314)
```

So map-join is faster in MRI, buffer is faster in JRuby, and RBX is just terribly slow at this string manipulation :( cc @brixen