[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/arrayfire/arrayfire/feature/optional-graphics/test/memory.cpp [Back]  [Original]

/*******************************************************
 * Copyright (c) 2014, ArrayFire
 * All rights reserved.
 *
 * This file is distributed under 3-clause BSD license.
 * The complete license agreement can be obtained at:
 * http://arrayfire.com/licenses/BSD-3-Clause
 ********************************************************/

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 

using af::alloc;
using af::allocV2;
using af::array;
using af::cdouble;
using af::cfloat;
using af::deviceGC;
using af::deviceMemInfo;
using af::dim4;
using af::dtype;
using af::dtype_traits;
using af::freeV2;
using af::randu;
using af::seq;
using af::span;
using std::vector;

const size_t step_bytes = 1024;

TEST(Memory, Scope) {
    size_t alloc_bytes, alloc_buffers;
    size_t lock_bytes, lock_buffers;

    cleanSlate();  // Clean up everything done so far

    {
        array a = randu(5, 5);

        deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

        ASSERT_EQ(alloc_buffers, 1u);
        ASSERT_EQ(lock_buffers, 1u);

        ASSERT_EQ(alloc_bytes, 1 * step_bytes);
        ASSERT_EQ(lock_bytes, 1 * step_bytes);
    }

    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 1u);
    ASSERT_EQ(lock_buffers, 0u);  // 0 because a is out of scope

    ASSERT_EQ(alloc_bytes, 1 * step_bytes);
    ASSERT_EQ(lock_bytes, 0u);
}

template
class MemAlloc : public ::testing::Test {
   public:
    virtual void SetUp() {}
};

// create a list of types to be tested
typedef ::testing::Types
    TestTypes;

// register the type list
TYPED_TEST_SUITE(MemAlloc, TestTypes);

size_t roundUpToStep(size_t bytes) {
    if (step_bytes == 0) return bytes;

    size_t remainder = bytes % step_bytes;
    if (remainder == 0) return bytes;

    return bytes + step_bytes - remainder;
}

template
void memAllocArrayScopeTest(int elements) {
    SUPPORTED_TYPE_CHECK(T);

    size_t alloc_bytes, alloc_buffers;
    size_t lock_bytes, lock_buffers;

    cleanSlate();  // Clean up everything done so far

    {
        array a = randu(elements, (af_dtype)dtype_traits::af_type);

        deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

        ASSERT_EQ(alloc_buffers, 1u);
        ASSERT_EQ(lock_buffers, 1u);

        ASSERT_EQ(alloc_bytes, roundUpToStep(elements * sizeof(T)));
        ASSERT_EQ(lock_bytes, roundUpToStep(elements * sizeof(T)));
    }

    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 1u);
    ASSERT_EQ(lock_buffers, 0u);  // 0 because a is out of scope

    ASSERT_EQ(alloc_bytes, roundUpToStep(elements * sizeof(T)));
    ASSERT_EQ(lock_bytes, 0u);
}

template
void memAllocPtrScopeTest(int elements) {
    SUPPORTED_TYPE_CHECK(T);

    size_t alloc_bytes, alloc_buffers;
    size_t lock_bytes, lock_buffers;

    cleanSlate();  // Clean up everything done so far
    {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
        T *ptr = alloc(elements);

        deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

        ASSERT_EQ(alloc_buffers, 1u);
        ASSERT_EQ(lock_buffers, 1u);

        ASSERT_EQ(alloc_bytes, roundUpToStep(elements * sizeof(T)));
        ASSERT_EQ(lock_bytes, roundUpToStep(elements * sizeof(T)));

        af::free(ptr);
#pragma GCC diagnostic pop
    }

    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 1u);
    ASSERT_EQ(lock_buffers, 0u);  // 0 because a is out of scope

    ASSERT_EQ(alloc_bytes, roundUpToStep(elements * sizeof(T)));
    ASSERT_EQ(lock_bytes, 0u);

    // Do without using templated alloc
    cleanSlate();  // Clean up everything done so far

    {
        void *ptr = allocV2(elements * sizeof(T));

        deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

        ASSERT_EQ(alloc_buffers, 1u);
        ASSERT_EQ(lock_buffers, 1u);

        ASSERT_EQ(alloc_bytes, roundUpToStep(elements * sizeof(T)));
        ASSERT_EQ(lock_bytes, roundUpToStep(elements * sizeof(T)));

        af::freeV2(ptr);
    }

    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 1u);
    ASSERT_EQ(lock_buffers, 0u);  // 0 because a is out of scope

    ASSERT_EQ(alloc_bytes, roundUpToStep(elements * sizeof(T)));
    ASSERT_EQ(lock_bytes, 0u);
}

TYPED_TEST(MemAlloc, ArrayScope25) { memAllocArrayScopeTest(25); }

TYPED_TEST(MemAlloc, ArrayScope2048) {
    memAllocArrayScopeTest(2048);
}

TYPED_TEST(MemAlloc, ArrayScope2293) {
    memAllocArrayScopeTest(2293);
}

TYPED_TEST(MemAlloc, PtrScope25) { memAllocPtrScopeTest(25); }

TYPED_TEST(MemAlloc, PtrScope2048) { memAllocPtrScopeTest(2048); }

TYPED_TEST(MemAlloc, PtrScope2293) { memAllocPtrScopeTest(2293); }

TEST(Memory, SingleSizeLoop) {
    size_t alloc_bytes, alloc_buffers;
    size_t lock_bytes, lock_buffers;

    cleanSlate();  // Clean up everything done so far

    {
        array a = randu(5, 5);

        deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

        ASSERT_EQ(alloc_buffers, 1u);
        ASSERT_EQ(lock_buffers, 1u);
        ASSERT_EQ(alloc_bytes, 1 * step_bytes);
        ASSERT_EQ(lock_bytes, 1 * step_bytes);

        for (int i = 0; i < 100; i++) {
            a = randu(5, 5);

            deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes,
                          &lock_buffers);

            ASSERT_EQ(
                alloc_buffers,
                2u);  // 2 because a new one is created before a is destroyed
            ASSERT_EQ(lock_buffers, 1u);
            ASSERT_EQ(alloc_bytes, 2 * step_bytes);
            ASSERT_EQ(lock_bytes, 1 * step_bytes);
        }
    }
}

TEST(Memory, LargeLoop) {
    size_t alloc_bytes, alloc_buffers;
    size_t lock_bytes, lock_buffers;

    cleanSlate();  // Clean up everything done so far

    const int num    = step_bytes / sizeof(float);
    size_t allocated = step_bytes;

    array a = randu(num);

    vector hA(num);

    a.host(&hA[0]);

    // Run a large loop that allocates more and more memory at each step
    for (int i = 0; i < 250; i++) {
        array b        = randu(num * (i + 1));
        size_t current = (i + 1) * step_bytes;
        allocated += current;

        // Verify that new buffers are being allocated
        deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

        // Limit to 10 to check before memory cleanup
        if (i < 10) {
            ASSERT_EQ(alloc_buffers, (size_t)(i + 2));  // i is zero based
            ASSERT_EQ(lock_buffers, 2u);

            ASSERT_EQ(alloc_bytes, allocated);
            ASSERT_EQ(lock_bytes, current + step_bytes);
        }
    }

    size_t old_alloc_bytes   = alloc_bytes;
    size_t old_alloc_buffers = alloc_buffers;

    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(old_alloc_bytes, alloc_bytes);
    ASSERT_EQ(old_alloc_buffers, alloc_buffers);

    ASSERT_EQ(lock_buffers, 1u);
    ASSERT_EQ(lock_bytes, 1 * step_bytes);
}

TEST(Memory, IndexingOffset) {
    size_t alloc_bytes, alloc_buffers;
    size_t lock_bytes, lock_buffers;

    cleanSlate();  // Clean up everything done so far

    const int num = step_bytes / sizeof(float);

    array a = randu(num);

    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 1u);
    ASSERT_EQ(lock_buffers, 1u);
    ASSERT_EQ(alloc_bytes, 1 * step_bytes);
    ASSERT_EQ(lock_bytes, 1 * step_bytes);

    {
        array b = a(seq(1, num / 2));  // Should just be an offset

        deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

        ASSERT_EQ(alloc_buffers, 1u);
        ASSERT_EQ(lock_buffers, 1u);
        ASSERT_EQ(alloc_bytes, 1 * step_bytes);
        ASSERT_EQ(lock_bytes, 1 * step_bytes);
    }

    // b should not have deleted a
    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 1u);
    ASSERT_EQ(lock_buffers, 1u);
    ASSERT_EQ(alloc_bytes, 1 * step_bytes);
    ASSERT_EQ(lock_bytes, 1 * step_bytes);
}

TEST(Memory, IndexingCopy) {
    size_t alloc_bytes, alloc_buffers;
    size_t lock_bytes, lock_buffers;

    cleanSlate();  // Clean up everything done so far

    const int num = step_bytes / sizeof(float);

    array a = randu(num);

    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 1u);
    ASSERT_EQ(lock_buffers, 1u);
    ASSERT_EQ(alloc_bytes, 1 * step_bytes);
    ASSERT_EQ(lock_bytes, 1 * step_bytes);

    {
        // Should just a copy
        array b = a(seq(0, num / 2 - 1, 2));

        deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

        ASSERT_EQ(alloc_buffers, 2u);
        ASSERT_EQ(lock_buffers, 2u);
        ASSERT_EQ(alloc_bytes, 2 * step_bytes);
        ASSERT_EQ(lock_bytes, 2 * step_bytes);
    }

    // b should not have deleted a
    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 2u);
    ASSERT_EQ(lock_buffers, 1u);
    ASSERT_EQ(alloc_bytes, 2 * step_bytes);
    ASSERT_EQ(lock_bytes, 1 * step_bytes);
}

TEST(Memory, Assign) {
    size_t alloc_bytes, alloc_buffers;
    size_t lock_bytes, lock_buffers;

    cleanSlate();  // Clean up everything done so far

    const int num = step_bytes / sizeof(float);

    array a = randu(num);

    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 1u);
    ASSERT_EQ(lock_buffers, 1u);
    ASSERT_EQ(alloc_bytes, 1 * step_bytes);
    ASSERT_EQ(lock_bytes, 1 * step_bytes);

    {
        array b         = randu(num / 2);
        a(seq(num / 2)) = b;

        deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

        ASSERT_EQ(alloc_buffers, 2u);
        ASSERT_EQ(lock_buffers, 2u);
        ASSERT_EQ(alloc_bytes, 2 * step_bytes);
        ASSERT_EQ(lock_bytes, 2 * step_bytes);
    }

    // b should not have deleted a
    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 2u);
    ASSERT_EQ(lock_buffers, 1u);
    ASSERT_EQ(alloc_bytes, 2 * step_bytes);
    ASSERT_EQ(lock_bytes, 1 * step_bytes);
}

TEST(Memory, AssignLoop) {
    size_t alloc_bytes, alloc_buffers;
    size_t lock_bytes, lock_buffers;

    cleanSlate();  // Clean up everything done so far

    const int num  = step_bytes / sizeof(float);
    const int cols = 100;

    array a = randu(num, cols);

    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 1u);
    ASSERT_EQ(lock_buffers, 1u);
    ASSERT_EQ(alloc_bytes, cols * step_bytes);
    ASSERT_EQ(lock_bytes, cols * step_bytes);

    for (int i = 0; i < cols; i++) {
        array b    = randu(num);
        a(span, i) = b;

        deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

        ASSERT_EQ(alloc_buffers,
                  2u);  // 3 because you need another scratch space for b
        ASSERT_EQ(lock_buffers, 2u);
        ASSERT_EQ(alloc_bytes, (cols + 1) * step_bytes);
        ASSERT_EQ(lock_bytes, (cols + 1) * step_bytes);
    }
}

TEST(Memory, AssignRef) {
    size_t alloc_bytes, alloc_buffers;
    size_t lock_bytes, lock_buffers;

    cleanSlate();  // Clean up everything done so far

    const int num = step_bytes / sizeof(float);

    array a     = randu(num);
    array a_ref = a;

    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 1u);
    ASSERT_EQ(lock_buffers, 1u);
    ASSERT_EQ(alloc_bytes, 1 * step_bytes);
    ASSERT_EQ(lock_bytes, 1 * step_bytes);

    {
        array b = randu(num / 2);
        // This should do a full copy of a
        a(seq(num / 2)) = b;

        deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

        ASSERT_EQ(alloc_buffers, 3u);
        ASSERT_EQ(lock_buffers, 3u);
        ASSERT_EQ(alloc_bytes, 3 * step_bytes);
        ASSERT_EQ(lock_bytes, 3 * step_bytes);
    }

    // b should not have deleted a
    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 3u);
    ASSERT_EQ(lock_buffers, 2u);  // a_ref
    ASSERT_EQ(alloc_bytes, 3 * step_bytes);
    ASSERT_EQ(lock_bytes, 2 * step_bytes);  // a_ref
}

TEST(Memory, AssignRefLoop) {
    size_t alloc_bytes, alloc_buffers;
    size_t lock_bytes, lock_buffers;

    cleanSlate();  // Clean up everything done so far

    const int num  = step_bytes / sizeof(float);
    const int cols = 100;

    array a     = randu(num, cols);
    array a_ref = a;

    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 1u);
    ASSERT_EQ(lock_buffers, 1u);
    ASSERT_EQ(alloc_bytes, cols * step_bytes);
    ASSERT_EQ(lock_bytes, cols * step_bytes);

    for (int i = 0; i < cols; i++) {
        array b    = randu(num);
        a(span, i) = b;

        deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

        ASSERT_EQ(alloc_buffers, 3u);
        ASSERT_EQ(lock_buffers, 3u);
        ASSERT_EQ(alloc_bytes, (2 * cols + 1) * step_bytes);
        ASSERT_EQ(lock_bytes, (2 * cols + 1) * step_bytes);
    }

    // b should not have deleted a
    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 3u);
    ASSERT_EQ(lock_buffers, 2u);  // a_ref
    ASSERT_EQ(alloc_bytes, (2 * cols + 1) * step_bytes);
    ASSERT_EQ(lock_bytes, 2 * cols * step_bytes);  // a_ref
}

TEST(Memory, device) {
    size_t alloc_bytes, alloc_buffers;
    size_t lock_bytes, lock_buffers;

    cleanSlate();  // Clean up everything done so far

    {
        array a = randu(5, 5);

        deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

        ASSERT_EQ(alloc_buffers, 1u);
        ASSERT_EQ(lock_buffers, 1u);
        ASSERT_EQ(alloc_bytes, 1 * step_bytes);
        ASSERT_EQ(lock_bytes, 1 * step_bytes);

        a.device();

        deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

        ASSERT_EQ(alloc_buffers, 1u);
        ASSERT_EQ(lock_buffers, 1u);
        ASSERT_EQ(alloc_bytes, 1 * step_bytes);
        ASSERT_EQ(lock_bytes, 1 * lock_bytes);

        a.unlock();  // to reset the lock flag
    }

    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 1u);
    ASSERT_EQ(lock_buffers, 0u);
    ASSERT_EQ(alloc_bytes, 1 * step_bytes);
    ASSERT_EQ(lock_bytes, 0u);
}

TEST(Memory, Assign2D) {
    size_t alloc_bytes, alloc_buffers;
    size_t alloc_bytes_after, alloc_buffers_after;
    size_t lock_bytes, lock_buffers;
    size_t lock_bytes_after, lock_buffers_after;

    cleanSlate();  // Clean up everything done so far
    {
        array a       = af::randu(10, 10, f32);
        unsigned hb[] = {3, 5, 6, 8, 9};
        array b(5, hb);
        array c = af::randu(5, f32);
        deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);
        a(b) = c;
    }

    deviceMemInfo(&alloc_bytes_after, &alloc_buffers_after, &lock_bytes_after,
                  &lock_buffers_after);

    // Check if assigned allocated extra buffers
    ASSERT_EQ(alloc_buffers, alloc_buffers_after);
    ASSERT_EQ(alloc_bytes, alloc_bytes_after);
}

TEST(Memory, unlock) {
    size_t alloc_bytes, alloc_buffers;
    size_t lock_bytes, lock_buffers;

    cleanSlate();  // Clean up everything done so far

    const dim_t num = step_bytes / sizeof(float);

    vector in(num);

    af_array arr = 0;
    ASSERT_SUCCESS(af_create_array(&arr, &in[0], 1, &num, f32));

    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 1u);
    ASSERT_EQ(lock_buffers, 1u);
    ASSERT_EQ(alloc_bytes, step_bytes);
    ASSERT_EQ(lock_bytes, step_bytes);

    // arr1 gets released by end of the following code block
    {
        array a(arr);
        a.lock();

        // No new memory should be allocated
        deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

        ASSERT_EQ(alloc_buffers, 1u);
        ASSERT_EQ(lock_buffers, 1u);
        ASSERT_EQ(alloc_bytes, step_bytes);
        ASSERT_EQ(lock_bytes, step_bytes);

        a.unlock();
    }

    // Making sure all unlocked buffers are freed
    deviceGC();

    deviceMemInfo(&alloc_bytes, &alloc_buffers, &lock_bytes, &lock_buffers);

    ASSERT_EQ(alloc_buffers, 0u);
    ASSERT_EQ(lock_buffers, 0u);
    ASSERT_EQ(alloc_bytes, 0u);
    ASSERT_EQ(lock_bytes, 0u);
}

TEST(Memory, IndexedDevice) {
    // This test is checking to see if calling .device() will force copy to a
    // new buffer
    const int nx = 8;
    const int ny = 8;

    array in = randu(nx, ny);

    vector in1(in.elements());
    in.host(&in1[0]);

    int offx = nx / 4;
    int offy = ny / 4;

    in = in(seq(offx, offx + nx / 2 - 1), seq(offy, offy + ny / 2 - 1));

    int nxo = (int)in.dims(0);
    int nyo = (int)in.dims(1);

    void *rawPtr = getRawPtr(in);
    void *devPtr = in.device();
    ASSERT_NE(devPtr, rawPtr);
    in.unlock();

    vector in2(in.elements());
    in.host(&in2[0]);

    for (int y = 0; y < nyo; y++) {
        for (int x = 0; x < nxo; x++) {
            ASSERT_EQ(in1[(offy + y) * nx + offx + x], in2[y * nxo + x]);
        }
    }
}

namespace {

template
T *getMemoryManagerPayload(af_memory_manager manager) {
    void *payloadPtr;
    af_memory_manager_get_payload(manager, &payloadPtr);
    return (T *)payloadPtr;
}

/**
 * An extremely basic memory manager with a basic caching mechanism for testing
 * purposes. It is not thread safe or optimized.
 */
struct E2ETestPayload {
    int initializeCalledTimes{0};
    int shutdownCalledTimes{0};
    std::unordered_map table;
    std::unordered_set locked;
    size_t totalBytes{0};
    size_t totalBuffers{0};
    size_t lockedBytes{0};
    unsigned lastNdims;
    dim4 lastDims;
    unsigned lastElementSize;

    size_t maxBuffers{64};
    size_t maxBytes{1024};
    // Print info args
    std::string printInfoStringArg;
    int printInfoDevice{-1};
};

af_err allocated_fn(af_memory_manager manager, size_t *out, void *ptr) {
    auto &table = getMemoryManagerPayload(manager)->table;
    if (table.find(ptr) == table.end()) {
        *out = 0;
    } else {
        *out = table[ptr];
    }
    return AF_SUCCESS;
}

af_err user_lock_fn(af_memory_manager manager, void *ptr) {
    auto *payload = getMemoryManagerPayload(manager);
    if (payload->locked.find(ptr) == payload->locked.end()) {
        payload->locked.insert(ptr);
        payload->lockedBytes += payload->table[ptr];
    }
    return AF_SUCCESS;
}

af_err is_user_locked_fn(af_memory_manager manager, int *out, void *ptr) {
    auto *payload = getMemoryManagerPayload(manager);
    *out          = payload->locked.find(ptr) != payload->locked.end();
    return AF_SUCCESS;
}

af_err unlock_fn(af_memory_manager manager, void *ptr, int userLock) {
    if (!ptr) { return AF_SUCCESS; }

    auto *payload = getMemoryManagerPayload(manager);

    if (payload->table.find(ptr) == payload->table.end()) {
        return AF_SUCCESS;  // fast path
    }

    // For testing, treat user-allocated and AF-allocated memory identically
    if (payload->locked.find(ptr) != payload->locked.end()) {
        payload->locked.erase(ptr);
        payload->lockedBytes -= payload->table[ptr];
    }
    return AF_SUCCESS;
}

af_err user_unlock_fn(af_memory_manager manager, void *ptr) {
    auto *payload = getMemoryManagerPayload(manager);
    af_err err    = unlock_fn(manager, ptr, /* user */ 1);
    payload->lockedBytes -= payload->table[ptr];
    return err;
}

af_err signal_memory_cleanup_fn(af_memory_manager manager) {
    auto *payload = getMemoryManagerPayload(manager);
    // Free unlocked memory
    std::vector freed;
    for (auto &entry : payload->table) {
        int isUserLocked;
        is_user_locked_fn(manager, &isUserLocked, entry.first);
        if (!isUserLocked) {
            void *ptr = entry.first;
            af_memory_manager_native_free(manager, ptr);
            payload->totalBytes -= payload->table[entry.first];
            freed.push_back(entry.first);
        }
    }
    for (auto ptr : freed) { payload->table.erase(ptr); }
    return AF_SUCCESS;
}

af_err print_info_fn(af_memory_manager manager, char *c, int b) {
    auto *payload = getMemoryManagerPayload(manager);
    payload->printInfoStringArg = std::string(c);
    payload->printInfoDevice    = b;
    return AF_SUCCESS;
}

af_err get_memory_pressure_fn(af_memory_manager manager, float *out) {
    auto *payload = getMemoryManagerPayload(manager);
    if (payload->lockedBytes > payload->maxBytes ||
        payload->totalBuffers > payload->maxBuffers) {
        *out = 1.0;
    } else {
        *out = 0.0;
    }
    return AF_SUCCESS;
}

af_err jit_tree_exceeds_memory_pressure_fn(af_memory_manager manager, int *out,
                                           size_t bytes) {
    auto *payload = getMemoryManagerPayload(manager);
    *out          = 2 * bytes > payload->totalBytes;
    return AF_SUCCESS;
}

af_err alloc_fn(af_memory_manager manager, void **ptr,
                /* bool */ int userLock, const unsigned ndims, dim_t *dims,
                const unsigned element_size) {
    size_t size = element_size;
    for (unsigned i = 0; i < ndims; ++i) { size *= dims[i]; }

    if (size > 0) {
        float pressure;
        get_memory_pressure_fn(manager, &pressure);
        float threshold;
        af_memory_manager_get_memory_pressure_threshold(manager, &threshold);
        if (pressure >= threshold) { signal_memory_cleanup_fn(manager); }

        if (af_err err = af_memory_manager_native_alloc(manager, ptr, size)) {
            return err;
        }

        auto *payload        = getMemoryManagerPayload(manager);
        payload->table[*ptr] = size;
        payload->totalBytes += size;
        payload->totalBuffers++;

        // Simple implementation: treat user and AF allocations the same
        payload->locked.insert(*ptr);
        payload->lockedBytes += size;

        payload->lastNdims       = ndims;
        payload->lastDims        = dim4(ndims, dims);
        payload->lastElementSize = element_size;
    }

    return AF_SUCCESS;
}

void add_memory_management_fn(af_memory_manager manager, int id) {}

void remove_memory_management_fn(af_memory_manager manager, int id) {}

}  // namespace

class MemoryManagerApi : public ::testing::Test {
   public:
    af_memory_manager manager;
    std::unique_ptr payload{new E2ETestPayload()};
    void SetUp() override {
        af_create_memory_manager(&manager);

        // Set payload_fn
        af_memory_manager_set_payload(manager, payload.get());

        auto initialize_fn = [](af_memory_manager manager) {
            auto *payload = getMemoryManagerPayload(manager);
            payload->initializeCalledTimes++;
            return AF_SUCCESS;
        };
        af_memory_manager_set_initialize_fn(manager, initialize_fn);

        auto shutdown_fn = [](af_memory_manager manager) {
            auto *payload = getMemoryManagerPayload(manager);
            payload->shutdownCalledTimes++;
            return AF_SUCCESS;
        };
        af_memory_manager_set_shutdown_fn(manager, shutdown_fn);

        // alloc
        af_memory_manager_set_alloc_fn(manager, alloc_fn);
        af_memory_manager_set_allocated_fn(manager, allocated_fn);
        af_memory_manager_set_unlock_fn(manager, unlock_fn);
        // utils
        af_memory_manager_set_signal_memory_cleanup_fn(
            manager, signal_memory_cleanup_fn);
        af_memory_manager_set_print_info_fn(manager, print_info_fn);
        // user lock/unlock
        af_memory_manager_set_user_lock_fn(manager, user_lock_fn);
        af_memory_manager_set_user_unlock_fn(manager, user_unlock_fn);
        af_memory_manager_set_is_user_locked_fn(manager, is_user_locked_fn);
        // memory pressure
        af_memory_manager_set_get_memory_pressure_fn(manager,
                                                     get_memory_pressure_fn);
        af_memory_manager_set_jit_tree_exceeds_memory_pressure_fn(
            manager, jit_tree_exceeds_memory_pressure_fn);
        // ocl
        af_memory_manager_set_add_memory_management_fn(
            manager, add_memory_management_fn);
        af_memory_manager_set_remove_memory_management_fn(
            manager, remove_memory_management_fn);

        af_set_memory_manager(manager);
    }

    void TearDown() override {
        af_device_gc();
        af_unset_memory_manager();
        af_release_memory_manager(manager);
    }
};

TEST_F(MemoryManagerApi, E2ETest1D) {
    size_t aSize = 8;

    array a = af::array(aSize, af::dtype::f32);
    ASSERT_EQ(payload->table.size(), 1);

    ASSERT_EQ(payload->table[a.device()], aSize * sizeof(float));
    ASSERT_EQ(payload->lastNdims, 1);
    ASSERT_EQ(payload->lastDims, af::dim4(aSize));
    ASSERT_EQ(payload->lastElementSize, 4);
}

TEST_F(MemoryManagerApi, E2ETest2D) {
    size_t aSize = 8;

    af::array a = af::array(aSize, aSize, af::dtype::f32);
    ASSERT_EQ(payload->table.size(), 1);
    ASSERT_EQ(payload->table[a.device()], aSize * aSize * sizeof(float));
    ASSERT_EQ(payload->lastElementSize, 4);

    // Currently this is set to 1 because all allocations request linear memory
    // This behavior will change in the future
    ASSERT_EQ(payload->lastNdims, 1);
    ASSERT_EQ(payload->lastDims, af::dim4(aSize * aSize));
}

TEST_F(MemoryManagerApi, E2ETest3D) {
    size_t aSize = 8;

    af::array a = af::array(aSize, aSize, aSize, af::dtype::f32);
    ASSERT_EQ(payload->table.size(), 1);
    ASSERT_EQ(payload->table[a.device()],
              aSize * aSize * aSize * sizeof(float));
    ASSERT_EQ(payload->lastElementSize, 4);

    // Currently this is set to 1 because all allocations request linear memory
    // This behavior will change in the future
    ASSERT_EQ(payload->lastNdims, 1);
    ASSERT_EQ(payload->lastDims, af::dim4(aSize * aSize * aSize));
}

TEST_F(MemoryManagerApi, E2ETest4D) {
    size_t aSize = 8;

    af::array a = af::array(aSize, aSize, aSize, aSize, af::dtype::f32);
    ASSERT_EQ(payload->table.size(), 1);
    ASSERT_EQ(payload->table[a.device()],
              aSize * aSize * aSize * aSize * sizeof(float));
    ASSERT_EQ(payload->lastElementSize, 4);

    // Currently this is set to 1 because all allocations request linear memory
    // This behavior will change in the future
    ASSERT_EQ(payload->lastNdims, 1);
    ASSERT_EQ(payload->lastDims, af::dim4(aSize * aSize * aSize * aSize));
    af::sync();
}

TEST_F(MemoryManagerApi, E2ETest4DComplexDouble) {
    SUPPORTED_TYPE_CHECK(double);
    size_t aSize = 8;

    af::array a = af::array(aSize, aSize, aSize, aSize, af::dtype::c64);
    ASSERT_EQ(payload->table.size(), 1);
    ASSERT_EQ(payload->table[a.device()],
              aSize * aSize * aSize * aSize * sizeof(double) * 2);
    ASSERT_EQ(payload->lastElementSize, 16);

    // Currently this is set to 1 because all allocations request linear memory
    // This behavior will change in the future
    ASSERT_EQ(payload->lastNdims, 1);
    ASSERT_EQ(payload->lastDims, af::dim4(aSize * aSize * aSize * aSize));
}

TEST_F(MemoryManagerApi, E2ETestMultipleAllocations) {
    SUPPORTED_TYPE_CHECK(double);
    size_t aSize = 8;

    af::array a = af::array(aSize, af::dtype::c64);
    ASSERT_EQ(payload->lastElementSize, 16);

    af::array b = af::array(aSize, af::dtype::f64);
    ASSERT_EQ(payload->lastElementSize, 8);

    ASSERT_EQ(payload->table.size(), 2);
    ASSERT_EQ(payload->table[a.device()], aSize * sizeof(double) * 2);
    ASSERT_EQ(payload->table[b.device()], aSize * sizeof(double));

    // Currently this is set to 1 because all allocations request linear memory
    // This behavior will change in the future
    ASSERT_EQ(payload->lastNdims, 1);
    ASSERT_EQ(payload->lastDims, af::dim4(aSize));
}

TEST_F(MemoryManagerApi, OutOfMemory) {
    af::array a;
    const unsigned N = 99999;
    try {
        a = af::randu({N, N, N}, af::dtype::f32);
        FAIL();
    } catch (af::exception &ex) {
        ASSERT_EQ(ex.err(), AF_ERR_NO_MEM);
    } catch (...) { FAIL(); }
}

TEST(MemoryManagerE2E, E2ETest) {
    af_memory_manager manager;
    af_create_memory_manager(&manager);

    // Set payload_fn
    std::unique_ptr payload(new E2ETestPayload());
    af_memory_manager_set_payload(manager, payload.get());

    auto initialize_fn = [](af_memory_manager manager) {
        auto *payload = getMemoryManagerPayload(manager);
        payload->initializeCalledTimes++;
        return AF_SUCCESS;
    };
    af_memory_manager_set_initialize_fn(manager, initialize_fn);

    auto shutdown_fn = [](af_memory_manager manager) {
        auto *payload = getMemoryManagerPayload(manager);
        payload->shutdownCalledTimes++;
        return AF_SUCCESS;
    };
    af_memory_manager_set_shutdown_fn(manager, shutdown_fn);

    // alloc
    af_memory_manager_set_alloc_fn(manager, alloc_fn);
    af_memory_manager_set_allocated_fn(manager, allocated_fn);
    af_memory_manager_set_unlock_fn(manager, unlock_fn);
    // utils
    af_memory_manager_set_signal_memory_cleanup_fn(manager,
                                                   signal_memory_cleanup_fn);
    af_memory_manager_set_print_info_fn(manager, print_info_fn);
    // user lock/unlock
    af_memory_manager_set_user_lock_fn(manager, user_lock_fn);
    af_memory_manager_set_user_unlock_fn(manager, user_unlock_fn);
    af_memory_manager_set_is_user_locked_fn(manager, is_user_locked_fn);
    // memory pressure
    af_memory_manager_set_get_memory_pressure_fn(manager,
                                                 get_memory_pressure_fn);
    af_memory_manager_set_jit_tree_exceeds_memory_pressure_fn(
        manager, jit_tree_exceeds_memory_pressure_fn);
    // ocl
    af_memory_manager_set_add_memory_management_fn(manager,
                                                   add_memory_management_fn);
    af_memory_manager_set_remove_memory_management_fn(
        manager, remove_memory_management_fn);

    af_set_memory_manager(manager);
    {
        size_t aSize = 8;

        void *a = af::allocV2(aSize * sizeof(float));
        ASSERT_EQ(payload->table.size(), 1);

        ASSERT_EQ(payload->table[a], aSize * sizeof(float));
        ASSERT_EQ(payload->lastNdims, 1);
        ASSERT_EQ(payload->lastDims, af::dim4(aSize) * sizeof(float));
        ASSERT_EQ(payload->lastElementSize, 1);

        dim_t bDim = 2;
        auto b     = af::randu({bDim, bDim});

        ASSERT_EQ(payload->totalBytes, aSize * sizeof(float) + b.bytes());
        ASSERT_EQ(payload->totalBuffers, 2);
        ASSERT_EQ(payload->lockedBytes, aSize * sizeof(float) + b.bytes());
        ASSERT_EQ(payload->locked.size(), 2);
        ASSERT_EQ(payload->lastNdims, 1);
        ASSERT_EQ(payload->lastDims, af::dim4(bDim * b.numdims()));
        ASSERT_EQ(payload->lastElementSize, sizeof(float));

        af::freeV2(a);

        ASSERT_EQ(payload->totalBytes, aSize * sizeof(float) + b.bytes());
        ASSERT_EQ(payload->totalBuffers, 2);
        ASSERT_EQ(payload->lockedBytes, b.bytes());
        ASSERT_EQ(payload->locked.size(), 1);
    }

    // gc
    af::deviceGC();
    ASSERT_EQ(payload->table.size(), 0);

    // printInfo
    std::string printInfoMsg = "testPrintInfo";
    int printInfoDeviceId    = 0;
    af::printMemInfo(printInfoMsg.c_str(), printInfoDeviceId);
    ASSERT_EQ(printInfoMsg, payload->printInfoStringArg);
    ASSERT_EQ(printInfoDeviceId, payload->printInfoDevice);

    // step size (throws with a custom memory manager)
    ASSERT_THROW(af::setMemStepSize(500), af::exception);
    ASSERT_THROW(af::getMemStepSize(), af::exception);

    ASSERT_EQ(payload->table.size(), 0);
    af_unset_memory_manager();
    af_release_memory_manager(manager);
    ASSERT_EQ(payload->initializeCalledTimes, 1);
    ASSERT_EQ(payload->shutdownCalledTimes, af::getDeviceCount());
}

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
TEST(Memory, AfAllocDeviceCPUC) {
    af_backend active_backend;
    ASSERT_SUCCESS(af_get_active_backend(&active_backend));

    if (active_backend == AF_BACKEND_CPU) {
        void *ptr;
        ASSERT_SUCCESS(af_alloc_device(&ptr, sizeof(float)));

        // This is the CPU backend so we can assign to the pointer
        *static_cast(ptr) = 5;
        ASSERT_SUCCESS(af_free_device(ptr));
    }
}
#pragma GCC diagnostic pop

TEST(Memory, AfAllocDeviceV2CPUC) {
    af_backend active_backend;
    ASSERT_SUCCESS(af_get_active_backend(&active_backend));

    if (active_backend == AF_BACKEND_CPU) {
        void *ptr;
        ASSERT_SUCCESS(af_alloc_device_v2(&ptr, sizeof(float)));

        // This is the CPU backend so we can assign to the pointer
        *static_cast(ptr) = 5;
        ASSERT_SUCCESS(af_free_device_v2(ptr));
    }
}

TEST(Memory, SNIPPET_AllocCPU) {
    af_backend active_backend;
    ASSERT_SUCCESS(af_get_active_backend(&active_backend));

    if (active_backend == AF_BACKEND_CPU) {
        //! [ex_alloc_v2_cpu]

        // Allocate one float and cast to float*
        void *ptr   = af::allocV2(sizeof(float));
        float *dptr = static_cast(ptr);

        // This is the CPU backend so we can assign to the pointer
        dptr[0] = 5.0f;
        freeV2(ptr);

        //! [ex_alloc_v2_cpu]

        ASSERT_EQ(*dptr, 5.0f);
    }
}

Web Proxy Viewer  |  New URL  |  Original Page