Search code examples
c++visual-c++com

CComSafeArray to Tab-Separated Values Suggestions for Speed Improvement


I have the need to convert a 2D CComSafe Array into a tab-separated values text stream. The array can contain an arbitrary number of values, potentially in the millions.

The relevant chunks of code is below.

I strongly suspect that the generate_response_from_data function is just an atrocious way to generate the output. But I have not found a concrete example of a better way. And yes, I have searched to the best of my ability.

I tried to figure out whether Boost Karma would be a better solution, but frankly, I just couldn't figure out how to apply it to my use case.

Can someone provide some input as to a faster method?

// This is a 2D CComSafeArray
template<typename T>
class MyDataArray : public CComSafeArray<T>
{
public:
    MyDataArray() : CComSafeArray<T>() {}

    const T* get_value_ptr(long row, long col) const // 0-based indices.
    {
        // To shave off a tiny bit of time, validity of m_psa, row, and col are assumed.
        // Not great but for our application, those are checked prior to call.
        return &static_cast<T*>(this->m_psa->pvData)[this->m_psa->rgsabound[1].cElements * col + row];
    }

    // Other stuff for this class.
};

inline std::string my_variant_to_string(const VARIANT* p_var)
{
    // Will only ever have VT_I4, VT_R8, VT_BSTR
    if (VT_I4 == p_var->vt)
        return boost::lexical_cast<std::string>(p_var->intVal); // Boost faster than other methods!!!

    if (VT_R8 == p_var->vt)
        return boost::lexical_cast<std::string>(p_var->dblVal); // Boost faster than other methods!!!

    if (VT_BSTR == p_var->vt)
    {
        std::wstring wstr(p_var->bstrVal, SysStringLen(p_var->bstrVal));
        return Utils::from_wide(wstr); // from_wide is a conversion function I created.
    }

    //if (VT_EMPTY == == p_var->vt) {} // Technically not needed.

    return "";
}

template<typename T>
bool generate_response_from_data(const MyDataArray<T>& data_array, std::stringstream& response_body)
{
    if (2 != data_array.GetDimensions())
        return false;

    long row_begin = data_array.GetLowerBound(0);
    long row_end = data_array.GetUpperBound(0);
    long col_begin = data_array.GetLowerBound(1);
    long col_end = data_array.GetUpperBound(1);

    if (row_end < row_begin || col_end < col_begin)
        return false;

    for (long r = row_begin; r <= row_end; ++r)
    {
        for (long c = col_begin; c <= col_end; ++c)
        {
            if (c > 0)
                response_body << '\t';

            response_body << my_variant_to_string(data_array.get_value_ptr(r, c));
        }
        response_body << '\n';
    }

    return true;
}

Solution

  • Thanks @MichaelGunter. Your suggestion to overload << results in a ~60% increase in speed! I convert from wstring for each value because the percentage of string values is fairly small (if any at all) compared to the numeric values and converting the entire stream may be a waste in most cases.

    EDITED to reflect direct conversion from BSTR to std::string using custom function.

    std::stringstream& operator<<(std::stringstream& s, const VARIANT* p_v)
    {
        if (VT_I4 == p_v->vt)
            s << p_v->intVal;
        else if (VT_R8 == p_v->vt)
            s << p_v->dblVal;
        else if (VT_BSTR == p_v->vt)
        {
            //std::wstring wstr(p_v->bstrVal, SysStringLen(p_v->bstrVal));
            s << Utils::from_bstr(p_v->bstrVal);
        }
        return s;
    }
    
    ...
    response_body << odata_array.get_value_ptr(r, c);