This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

[参考译文] TDA4VM:C7X 上的 uDMA 慢于 A72上的 memcpy

Guru**** 2535750 points


请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1187347/tda4vm-udma-on-c7x-is-slower-than-memcpy-on-a72

器件型号:TDA4VM

您好!

我 已经创建了一个 openvx 内核 ,它将一个 vx_tensor 复制到另一个。  每个内核都指定为内核的目标、唯一的区别是 A72使用 memcpy、C7X、C66、R5F 使用 uDMA。

下面是我复制一个大小为864*128*sizeof(float)=442368字节的 vx_tensor 得到的数字

内核 memcpy
appUdmaCopy1D
a72. 670 μ s 不适用
C66 4889 us 2237 us
C7X 内核上运行 1616 μ s 2003 US
R5F 18097 us 5794 us

我得到的数字非常令人失望、UDMA 似乎比做 A72 memcpy 慢。 在 C7x 上、执行 mempcy 时甚至会更慢。

这些数字是否正常? 我没有找到 uDMA 的任何规格。

以下是我的(简化的)内核代码:

bool TensorBlockCpyDma(const size_t nBytes,
                       const tivx_obj_desc_tensor_t* const srcDescriptor,
                       const tivx_obj_desc_tensor_t* const dstDescriptor)
{
    bool ret = false;
    if (   (0U   == nBytes)
        || (NULL == srcDescriptor)
        || (NULL == dstDescriptor))
    {
        VX_PRINT(VX_ZONE_ERROR, "Invalid input pointer\n");
    }
    else
    {
        uint64_t srcPhys = tivxMemShared2PhysPtr(srcDescriptor->mem_ptr.shared_ptr, VX_MEMORY_TYPE_HOST);
        uint64_t dstPhys = tivxMemShared2PhysPtr(dstDescriptor->mem_ptr.shared_ptr, VX_MEMORY_TYPE_HOST);

        app_udma_copy_1d_prms_t prms;
        appUdmaCopy1DPrms_Init(&prms);
        prms.dest_addr = dstPhys;
        prms.src_addr = srcPhys;
        prms.length = nBytes;

        if (0 == appUdmaCopy1D(NULL, &prms))
        {
            ret = true;
        }
    }
    return ret;
}

static vx_status VX_CALLBACK tivxTensorcpyProcess(
       tivx_target_kernel_instance kernel,
       tivx_obj_desc_t *obj_desc[],
       uint16_t num_params, void *priv_arg)
{
    vx_status status = (vx_status)VX_SUCCESS;
    const tivx_obj_desc_tensor_t *src_desc;
    const tivx_obj_desc_tensor_t *dst_desc;

    if ( (num_params != TIVX_KERNEL_TENSORCPY_MAX_PARAMS)
        || (NULL == obj_desc[TIVX_KERNEL_TENSORCPY_SRC_IDX])
        || (NULL == obj_desc[TIVX_KERNEL_TENSORCPY_DST_IDX])
    )
    {
        status = (vx_status)VX_FAILURE;
    }

    if((vx_status)VX_SUCCESS == status)
    {
        src_desc = (const tivx_obj_desc_tensor_t *)obj_desc[TIVX_KERNEL_TENSORCPY_SRC_IDX];
        dst_desc = (const tivx_obj_desc_tensor_t *)obj_desc[TIVX_KERNEL_TENSORCPY_DST_IDX];
    }

    if((vx_status)VX_SUCCESS == status)
    {
        void *src_target_ptr;
        void *dst_target_ptr;

        src_target_ptr = tivxMemShared2TargetPtr(&src_desc->mem_ptr);
        tivxCheckStatus(&status, tivxMemBufferMap(src_target_ptr,
           src_desc->mem_size, (vx_enum)VX_MEMORY_TYPE_HOST,
           (vx_enum)VX_READ_ONLY));
        dst_target_ptr = tivxMemShared2TargetPtr(&dst_desc->mem_ptr);
        tivxCheckStatus(&status, tivxMemBufferMap(dst_target_ptr,
           dst_desc->mem_size, (vx_enum)VX_MEMORY_TYPE_HOST,
           (vx_enum)VX_WRITE_ONLY));

        {
            /* call kernel processing function */
            uint32_t start = tivxPlatformGetTimeInUsecs();
#ifdef A72
            memcpy(dst_target_ptr, src_target_ptr, src_desc->mem_size);
#else
            if (!TensorBlockCpyDma(src_desc->mem_size, src_desc, dst_desc))
            {
                VX_PRINT(VX_ZONE_ERROR, "TensorBlockCpyDma failed\n");
            }
#endif
            uint32_t delta = tivxPlatformGetTimeInUsecs() - start;
            VX_PRINT(VX_ZONE_WARNING, "TensorBlockCpyDma copied %u bytes in %u us\n", src_desc->mem_size, delta);
            /* kernel processing function complete */

        }
        tivxCheckStatus(&status, tivxMemBufferUnmap(src_target_ptr,
           src_desc->mem_size, (vx_enum)VX_MEMORY_TYPE_HOST,
            (vx_enum)VX_READ_ONLY));
        tivxCheckStatus(&status, tivxMemBufferUnmap(dst_target_ptr,
           dst_desc->mem_size, (vx_enum)VX_MEMORY_TYPE_HOST,
            (vx_enum)VX_WRITE_ONLY));
    }

    return status;
}

A72内核调用 memcpy()而不是 appUdmaCopy1D()。

谢谢!

弗雷德

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    尊敬的 Fred:

    A72 memcpy 优于 udma 副本、因为副本的大小很小。 只是450KB、可能 完全存储在缓存中、并且可以提供比 UDMA 更好的性能。 您能否尝试复制2MB 以上的内存? 我认为在本例中、uDMA 的性能将优于 A72上的 memcpy。  

    在 C7x 上、能否尝试使用 DRU 通道而不是 uDMA 通道?  

    此致、

    Brijesh

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    感谢您的回答、Brijesh。

    我尝试的 TensorCpy 是原始大小的5倍(2211840字节)、但 A72 memcpy 的性能仍然远远超过 DMA。

    内核 864 * 128 * 5 * sizeof (float)
    a72 memcpy 2033 us
    C66 DMA 9460us
    C7X DMA (DRU) 9074 us
    R5F DMA 搭配使用 13582 us

     

    我通过在 TensorBlockCpyDma()中添加这段代码,为 C7X 使用了 DRU :

    app_udma_ch_handle_t handle = NULL;
    #ifdef C71
    handle = appUdmaCopyNDGetHandle(8U);
    #endif
    if (0 == appUdmaCopy1D(handle, &prms))
    {
        ret = true;
    }

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好、Brijesh、这有什么更新吗?

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    您好、Brijesh、您能继续吗?

    在与 Kai 交谈后,她说 DRU 应该比 来自 C7X 的 memcpy 更快,所以我们需要找到 问题所在。 有任何迹象吗?

    谢谢。

    弗雷德

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    尊敬的

    是的、DRU 性能应 优于 A72 memcpy 性能。 您确定测试实际使用的是 DRU 通道吗? 您能说明一下您是如何启用 DRU 通道进行此测试的吗? 如果可能、请共享该代码片段?  

    这是因为非 DRU 和 DRU DMA 副本的性能数据看起来非常相似(9.4ms 与9ms)、因此我怀疑是否真正使用了 DRU。  

    此外、在 A72侧、您如何执行 memcpy? 如何为 src 和 dst 缓冲区分配存储器? 如果可能、您能否也在此处分享该代码?

     

    此致、

    Brijesh

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    尊敬的 Brijesh:

    要启用 DRU,我向 appUdmaCopyNDGetHandle()添加了呼叫,其中信道为8U。 下面是 我的 TensorCpyDma 函数:

    typedef struct
    {
        uint32_t elems;
        uint32_t srcOffset;
        uint32_t dstOffset;
    } TensorCpyCfg;
    
    bool TensorBlockCpyDma(const TensorCpyCfg* const cfg,
                           const tivx_obj_desc_tensor_t* const srcDescriptor,
                           const tivx_obj_desc_tensor_t* const dstDescriptor,
                           const void* restrict const srcMapped,
                           void* restrict const dstMapped)
    {
        bool ret = false;
        if (   (NULL == cfg)
            || (NULL == srcDescriptor)
            || (NULL == dstDescriptor)
            || (NULL == srcMapped)
            || (NULL == dstMapped))
        {
            VX_PRINT(VX_ZONE_ERROR, "Invalid input pointer\n");
        }
        else if (0U == cfg->elems)
        {
            VX_PRINT(VX_ZONE_ERROR, "Empty copy\n");
        }
        else
        {
            const uint32_t transferSize = cfg->elems * srcDescriptor->stride[0];
            const uint32_t srcAvailableSize = srcDescriptor->mem_size - cfg->srcOffset * srcDescriptor->stride[0];
            const uint32_t dstAvailableSize = dstDescriptor->mem_size - cfg->dstOffset * dstDescriptor->stride[0];
            if (   (transferSize > srcAvailableSize)
                || (transferSize > dstAvailableSize))
            {
                VX_PRINT(VX_ZONE_ERROR, "Invalid size/offset combination\n");
            }
            else
            {
    
                const uint32_t srcOffset = cfg->srcOffset*srcDescriptor->stride[0];
                const uint32_t dstOffset = cfg->dstOffset*dstDescriptor->stride[0];
    
                uint64_t srcPhys = tivxMemShared2PhysPtr(srcDescriptor->mem_ptr.shared_ptr, VX_MEMORY_TYPE_HOST) + srcOffset;
                uint64_t dstPhys = tivxMemShared2PhysPtr(dstDescriptor->mem_ptr.shared_ptr, VX_MEMORY_TYPE_HOST) + dstOffset;
    
                uint32_t start = tivxPlatformGetTimeInUsecs();
                app_udma_copy_1d_prms_t prms;
                appUdmaCopy1DPrms_Init(&prms);
                uint32_t delta = tivxPlatformGetTimeInUsecs() - start;
                VX_PRINT(VX_ZONE_WARNING, "appUdmaCopy1DPrms_Init = %u us\n", delta);
                prms.dest_addr = dstPhys;
                prms.src_addr = srcPhys;
                prms.length = transferSize;
    
                app_udma_ch_handle_t handle = NULL;
                #ifdef C71
                start = tivxPlatformGetTimeInUsecs();
                handle = appUdmaCopyNDGetHandle(8U);
                delta = tivxPlatformGetTimeInUsecs() - start;
                VX_PRINT(VX_ZONE_WARNING, "appUdmaCopyNDGetHandle = %u us\n", delta);
                #endif
                start = tivxPlatformGetTimeInUsecs();
                if (0 == appUdmaCopy1D(handle, &prms))
                {
                    // void* dstVirt = (uint8_t*) dstMapped + dstOffset;
                    // appMemCacheInv(dstVirt, transferSize);
                    ret = true;
                }
                delta = tivxPlatformGetTimeInUsecs() - start;
                VX_PRINT(VX_ZONE_WARNING, "appUdmaCopy1D = %u us\n", delta);
            }
        }
        return ret;
    }

    DRU 的新逻辑位于包含"#ifdef C71"的模块中

    至于 A72 memcpy、则是在 TIOVX A72内核中完成的。 分配在 C++ Catch2测试中完成:

    TEST_CASE("TensorCpy - Performance comparison)
    {
    	REQUIRE(0 == appInit());
    	tivxRegisterMemoryTargetA72Kernels();	// Registers the A72 memcpy kernel
    
    	std::string target = TIVX_TARGET_A72_0;
    	std::string impl = "CPU";
    
    	const size_t COLS = 864;
    	const size_t ROWS = 128 * 5;
    	std::vector<vx_size> dims{COLS, ROWS};
    
    	TiovxUserKernelsTests::VxTensorWrapper src("TensorCpy src", dims);
    	TiovxUserKernelsTests::VxTensorWrapper dst("TensorCpy dst", dims);
    	TiovxUserKernelsTests::VxUserDataObjectWrapper<TensorCpyCfg> cfg("TensorCpyCfg");
    
    	TensorCpyGraph graph(cfg, src, dst, target);	// Calls vxCreateContext()
    
    	src.Allocate(graph.ctx);						// Calls vxCreateTensor()
    	dst.Allocate(graph.ctx);						// Calls vxCreateTensor()
    
    	float* srcptr = src.Map(VX_WRITE_ONLY);			// Calls tivxMapTensorPatch()
    	float* dstptr = dst.Map(VX_WRITE_ONLY);			// Calls tivxMapTensorPatch()
    	for (size_t i = 0; i < COLS*ROWS; i++)
    	{
    		srcptr[i] = i;
    		dstptr[i] = 0;
    	}
    	src.Unmap();									// Calls tivxUnMapTensorPatch()
    	dst.Unmap();									// Calls tivxUnMapTensorPatch()
    
    	TensorCpyCfg cfgData;
    	cfgData.elems = COLS * ROWS;
    	cfgData.srcOffset = 0;
    	cfgData.dstOffset = 0;
    	cfg.Allocate(graph.ctx, &cfgData);				// Calls vxCreateUserDataObject()
    
    	graph.Allocate();								// Calls vxCreateGraph()
    	graph.Verify();									// Calls vxVerifyGraph()
    	graph.Run();									// Calls vxProcessGraph()
    	graph.PrintStats();								// Calls tivx_utils_graph_perf_print()
    
    	srcptr = src.Map(VX_READ_ONLY);					// Calls tivxMapTensorPatch()
    	dstptr = dst.Map(VX_READ_ONLY);					// Calls tivxMapTensorPatch()
    
    	REQUIRE(0 == memcmp(srcptr, dstptr, COLS*ROWS*sizeof(float)));
    
    	src.Delete();									// Calls vxReleaseTensor()
    	dst.Delete();									// Calls vxReleaseTensor()
    	cfg.Delete();									// Calls vxReleaseUserDataObject()
    
    	graph.Delete();									// Calls vxReleaseGraph(), vxReleaseContext()
    
    	tivxUnRegisterMemoryTargetA72Kernels();			// Unregisters A72 memcpy kernel
    	REQUIRE(0 == appDeInit());
    }

    C7X 内核的分配过程完全相同。

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    尊敬的

    根据 以下链接上的数据表、当我们使用来自任何内核的 DRU 通道时、我们应获得大约10Gb/s 这意味着、对于上面大约0.5Mb 的数据、大约花费0.5ms。  

    https://software-dl.ti.com/jacinto7/esd/processor-sdk-rtos-jacinto7/latest/exports/docs/pdk_jacinto_08_05_00_36/docs/datasheet/jacinto/datasheet_j721e.html#udma

    这是它花费更多时间的原因、可能是由于未启用中断。 在 APIappUdmaCopyNDGetHandle 中、我看到通过将标志 udmaCreatePrms.enable_intr 设置为0来禁用中断、当设置为0时、任务通过执行 TaskP_yield 一直等待。 这可能无法提供良好的性能、因为可能还有其他相同或更高优先级的任务正在运行、不允许其展开。  

    我认为最好在启用中断的情况下检查性能、但 不确定这是否受 DRU 通道支持/验证。  

    此致、

    Brijesh

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    在 appUdmaCreatePrms_Init ()(我使用)中,有以下注释:

    /* C7x 尚不支持中断模式-使用轮询*/
    我想这意味着 vision_apps API 尚未 准备好进行高效的 DRU 传输。
    是否有其他您可以让我参考的 API 来了解如何将 DRU 与 C7X 配合使用?  
  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    尊敬的

    确切地说、在没有中断模式的情况下、默认性能在默认代码中不是很好。  

    目前、只是为了测量性能、您能否 在 appUdmaTransfer 中注释掉对 TaskP_Yield API 的调用、然后检查性能?  

    此致、

    Brijesh

  • 请注意,本文内容源自机器翻译,可能存在语法或其它翻译错误,仅供参考。如需获取准确内容,请参阅链接中的英语原文或自行翻译。

    尊敬的 Brijesh、我们使用 ND API 而不是1D/2D 找到了一种权变措施

    appUdmaCopyNDPrms_Init()
    appUdmaCopyNDInit()
    appUdmaCopyNDTrigg()
    appUdmaCopyNDWait()
    appUdmaCopyNDDeinit()
    我认为 vision_apps 附带的一些 app_udma 存在性能问题。