Во-первых, в C# есть указатели, только тс-с-с! Об этом лучше не распространяться, особенно среди молодёжи, потому что это будет unsafe код со всеми вытекающими последствиями.
Во-вторых, со времён C известно, что арифметика указателей положительно сказывается на быстродействии. И поэтому в С выражение a[i] есть эквивалент *(a+i). Про C++ и тем более C# такого сказать уже нельзя: оператор [] может быть перегружен и нагружен всяческими дополнительными вещами, как-то проверки диапазонов. Кроме того, в C# объекты под управлением сборщика мусора могут перемещаться по памяти с целью дефрагментации, и решивший вдруг поработать сборщик мусора не оставит шансов добиться быстродействия.
Однако и в C# есть неафишируемые подходы, позволяющие выполнять код без проверок и с простой арифметикой. Но всё равно C/C++ за счёт эффективности компилятора предпочтительнее для интенсивных вычислений, нежели C#.
Итак, общая инфраструктура:
using System;
using System.Runtime.InteropServices;
namespace Test
{
    class Program
    {
        delegate void Test();
        const int mx = 10000, my = 10000, md = 1;
        static short[] a = new short[mx * my];
        static double[] b = new double[mx * my];
        unsafe static void Perform(Test test)
        {
            double bestTime = double.MaxValue;
            for (int i = 0; i < 3; ++i)
            {
                DateTime begin = DateTime.Now;
                test();
                DateTime end = DateTime.Now;
                double time = (end - begin).TotalSeconds;
                if(time < bestTime)
                {
                    bestTime = time;
                }
            }
            Console.WriteLine("{0:##.###} s ", bestTime);
        }
        unsafe static void Main(string[] args)
        {
            for (int y = 0; y < my; ++y)
            for (int x = 0; x < mx; ++x)
            {
                a[y * mx + x] = 1;
            }
            Perform(Test1);
            Perform(Test2);
            Perform(Test3);
            Perform(Test4);
            Console.ReadKey();
        }
    }
}
#include <climits>
#include <iostream>
#include <Windows.h>
using namespace std;
typedef void (*Test)();
const int mx = 10000, my = 10000, md = 1;
short *a;
double *b;
void Perform(Test test)
{
    double bestTime = DBL_MAX;
    for (int i = 0; i < 3; ++i)
    {
        DWORD begin = GetTickCount();
        test();
        DWORD end = GetTickCount();
        double time = (end - begin) / 1000.0;
        if(time < bestTime)
        {
            bestTime = time;
        }
    }
    std::cout << bestTime << " s " << std::endl;;
}
int main()
{
    a = new short[mx * my];
    b = new double[mx * my];
    for (int y = 0; y < my; ++y)
    for (int x = 0; x < mx; ++x)
    {
        a[y * mx + x] = 1;
    }
    Perform(Test1);
    Perform(Test2);
    //Perform(Test3);
    Perform(Test4);
    delete[] a;
    delete[] b;
    cin.get();
    return 0;
}
Теперь сравниваем алгоритм, задача которого в массиве b записать усреднения 3х3 областей из a в обратном порядке. Замеры времени работы производились на одной и той же машине, платформа x86. C++ и C# из VS 2012, .NET 4.5, оптимизации включены в обоих случаях, сборка Release.
Первый тест: решение в лоб
        static void Test1()
        {
            for (int y = md; y < my - md; ++y)
            for (int x = md; x < mx - md; ++x)
            {
                b[(my - y - 1) * mx + (mx - x - 1)] = 0.0;
                for (int dy = -md; dy <= md; ++dy)
                for (int dx = -md; dx <= md; ++dx)
                {
                    b[(my - y - 1) * mx + (mx - x - 1)] += a[(y + dy) * mx + (x + dx)];
                }
                b[(my - y - 1) * mx + (mx - x - 1)] /= (md * 2 + 1) * (md * 2 + 1);
            }
        }
 
Работает 9.7 секунды.
void Test1()
{
    for (int y = md; y < my - md; ++y)
    for (int x = md; x < mx - md; ++x)
    {
        b[(my - y - 1) * mx + (mx - x - 1)] = 0.0;
        for (int dy = -md; dy <= md; ++dy)
        for (int dx = -md; dx <= md; ++dx)
        {
            b[(my - y - 1) * mx + (mx - x - 1)] += a[(y + dy) * mx + (x + dx)];
        }
        b[(my - y - 1) * mx + (mx - x - 1)] /= (md * 2 + 1) * (md * 2 + 1);
    }
}
 
Работает 1.3 секунды.
Второй тест: попытка сократить вычисления, вручную вытаскивая из циклов повторяющиеся расчёты и устраняя выделение/удаление переменных на стеке.
        static void Test2()
        {
            int x, y, dx, dy, oa, ob, pb, oda, myl, mxl, s;
            myl = my - md;
            mxl = mx - md;
            s = md * 2 + 1;
            s *= s;
            for (y = md; y < myl; ++y)
            {
                oa = y * mx;
                ob = (my - y - 1) * mx;
                for (x = md; x < mxl; ++x)
                {
                    pb = ob + (mx - x - 1);
                    b[pb] = 0.0;
                    for (dy = -md; dy <= md; ++dy)
                    {
                        oda = oa + dy * mx;
                        for (dx = -md; dx <= md; ++dx)
                        {
                            b[pb] += a[oda + (x + dx)];
                        }
                    }
                    b[pb] /= s;
                }
            }
        }
Работает 8.9 секунды.
void Test2()
{
    int x, y, dx, dy, oa, ob, pb, oda, myl, mxl, s;
    myl = my - md;
    mxl = mx - md;
    s = md * 2 + 1;
    s *= s;
    for (y = md; y < myl; ++y)
    {
        oa = y * mx;
        ob = (my - y - 1) * mx;
        for (x = md; x < mxl; ++x)
        {
            pb = ob + (mx - x - 1);
            b[pb] = 0.0;
            for (dy = -md; dy <= md; ++dy)
            {
                oda = oa + dy * mx;
                for (dx = -md; dx <= md; ++dx)
                {
                    b[pb] += a[oda + (x + dx)];
                }
            }
            b[pb] /= s;
        }
    }
}
Работает 1.3 секунды.
Компилятор C++ все эти усовершенствования игнорирует - и правильно делает. Комплиятор C# тоже практически игнорирует, хотя 5% выиграли.
В C# можно особой директивой отключить разные проверки переполнения и т.п. В C++ аналога нет, поэтому третий тест лишь в одном варианте:
        static void Test3()
        {
            unchecked
            {
                int x, y, dx, dy, oa, ob, pb, oda, myl, mxl, s;
                myl = my - md;
                mxl = mx - md;
                s = md * 2 + 1;
                s *= s;
                for (y = md; y < myl; ++y)
                {
                    oa = y * mx;
                    ob = (my - y - 1) * mx;
                    for (x = md; x < mxl; ++x)
                    {
                        pb = ob + (mx - x - 1);
                        b[pb] = 0.0;
                        for (dy = -md; dy <= md; ++dy)
                        {
                            oda = oa + dy * mx;
                            for (dx = -md; dx <= md; ++dx)
                            {
                                b[pb] += a[oda + (x + dx)];
                            }
                        }
                        b[pb] /= s;
                    }
                }
            }
        }
Работает 9.2 секунды, т.е. разницы с предыдущим тестом фактически нет, а может даже и стало хуже.
Наконец, последний тест без оператора [] с арифметикой указателей.
        unsafe static void Test4()
        {
            unchecked
            {
                GCHandle ha = GCHandle.Alloc(a, GCHandleType.Pinned);
                GCHandle hb = GCHandle.Alloc(b, GCHandleType.Pinned);
                short* pa = (short*)ha.AddrOfPinnedObject();
                double* pb = (double*)hb.AddrOfPinnedObject();
                int x1 = md, xn = (mx - 1) - md;
                int y1 = md * mx, yn = (my - 1 - md) * mx;
                int dx1 = -md, dxn = md;
                int dy1 = -md * mx, dyn = md * mx;
                short* pya, pan, pxya, pyan, pdy, pdn, pdxy, pdyn;
                double* pyb, pxyb;
                int s;
                s = md * 2 + 1;
                s *= s;
                for (pya = pa + y1, pan = pa + yn, pyb = pb + yn; pya <= pan; pya += mx, pyb -= mx)
                for (pxya = pya + x1, pyan = pya + xn, pxyb = pyb + xn; pxya <= pyan; ++pxya, --pxyb)
                {
                    *pxyb = 0.0;
                    for (pdy = pxya + dy1, pdn = pxya + dyn; pdy <= pdn; pdy += mx)
                    for (pdxy = pdy + dx1, pdyn = pdy + dxn; pdxy <= pdyn; ++pdxy)
                    {
                        *pxyb += *pdxy;
                    }
                    *pxyb /= s;
                }
                ha.Free();
                hb.Free();
            }
        }
Работает 6.9 секунды.
void Test4()
{
    short *pa = a;
    double *pb = b;
    int x1 = md, xn = (mx - 1) - md;
    int y1 = md * mx, yn = (my - 1 - md) * mx;
    int dx1 = -md, dxn = md;
    int dy1 = -md * mx, dyn = md * mx;
    short *pya, *pan, *pxya, *pyan, *pdy, *pdn, *pdxy, *pdyn;
    double *pyb, *pxyb;
    int s;
    s = md * 2 + 1;
    s *= s;
    for (pya = pa + y1, pan = pa + yn, pyb = pb + yn; pya <= pan; pya += mx, pyb -= mx)
    for (pxya = pya + x1, pyan = pya + xn, pxyb = pyb + xn; pxya <= pyan; ++pxya, --pxyb)
    {
        *pxyb = 0.0;
        for (pdy = pxya + dy1, pdn = pxya + dyn; pdy <= pdn; pdy += mx)
        for (pdxy = pdy + dx1, pdyn = pdy + dxn; pdxy <= pdyn; ++pdxy)
        {
            *pxyb += *pdxy;
        }
        *pxyb /= s;
    }
}
Работает 4.5 секунды.
Из чего следует, что автоматическую оптимизацию компилятора C++ мы своей ручной безнадёжно сломали, а вот для C# получили заметное ускорение. Причём несложным экспериментом можно убедиться, что оно зависит исключительно от неиспользования оператора [], а не от защиты массивов от активности сборщика мусора.
Любопытно также, что Debug сборка C++ и Release сборка C# работают одинаково по времени. Ну это лишь совпадение.
Также стоит отметить, что при объявлении указателей звёздочка * в C# - элемент имени типа, а в C++ - элемент декларации переменной. На это можно напороться, если объявлять много переменных в одной строке через запятую - см. выше.
Мораль:
- Если нужны интенсивные вычисления с беганием по большим и не влезающим в кэш массивам (например, при обработке видеопотоков), лучше их писать на C++.
- Если в подобных задачах от C# отказаться никак, то ручная оптимизация за счёт выноса повторно используемых значений во внешние циклы и использование указателей, как показано выше, способно дать некоторое ускорение примерно на 20-25%.