From a84ba8fc0fe4665047fdce1c2d84b0e8a52aa441 Mon Sep 17 00:00:00 2001 From: nihuini Date: Fri, 5 Jan 2018 15:49:45 +0800 Subject: [PATCH] element type storage support in Mat, move data member the first so that a pointer to Mat is a pointer to data, convenient index access for float vector --- examples/fasterrcnn.cpp | 16 +- examples/squeezenet.cpp | 2 +- src/layer/arm/convolution_3x3.h | 2 +- src/layer/arm/convolutiondepthwise_arm.cpp | 8 +- src/layer/arm/deconvolution_3x3.h | 4 +- src/layer/arm/deconvolution_4x4.h | 4 +- src/layer/arm/deconvolutiondepthwise_arm.cpp | 8 +- src/layer/arm/innerproduct_arm.cpp | 4 +- src/layer/arm/slice_arm.cpp | 4 +- src/layer/batchnorm.cpp | 19 +- src/layer/bias.cpp | 3 +- src/layer/binaryop.cpp | 58 ++--- src/layer/convolution.cpp | 5 +- src/layer/convolutiondepthwise.cpp | 8 +- src/layer/deconvolution.cpp | 7 +- src/layer/deconvolutiondepthwise.cpp | 10 +- src/layer/detectionoutput.cpp | 2 +- src/layer/eltwise.cpp | 8 +- src/layer/embed.cpp | 4 +- src/layer/flatten.cpp | 2 +- src/layer/innerproduct.cpp | 5 +- src/layer/lstm.cpp | 24 +-- src/layer/mvn.cpp | 22 +- src/layer/normalize.cpp | 25 +-- src/layer/pooling.cpp | 3 +- src/layer/prelu.cpp | 4 +- src/layer/priorbox.cpp | 6 +- src/layer/proposal.cpp | 22 +- src/layer/reduction.cpp | 36 ++-- src/layer/rnn.cpp | 12 +- src/layer/scale.cpp | 12 +- src/layer/slice.cpp | 9 +- src/layer/softmax.cpp | 32 ++- src/layer/spp.cpp | 4 +- src/layer/unaryop.cpp | 4 +- src/mat.cpp | 16 +- src/mat.h | 211 ++++++++++++------- src/paramdict.cpp | 23 +- 38 files changed, 318 insertions(+), 330 deletions(-) diff --git a/examples/fasterrcnn.cpp b/examples/fasterrcnn.cpp index 369505dfa..c3091f20e 100644 --- a/examples/fasterrcnn.cpp +++ b/examples/fasterrcnn.cpp @@ -142,9 +142,9 @@ static int detect_fasterrcnn(const cv::Mat& bgr, std::vector& objects) in.substract_mean_normalize(mean_vals, 0); ncnn::Mat im_info(3); - im_info.data[0] = h; - im_info.data[1] = w; - im_info.data[2] = scale; + im_info[0] = h; + im_info[1] = w; + im_info[2] = scale; // step1, extract feature and all rois ncnn::Extractor ex1 = fasterrcnn.create_extractor(); @@ -182,7 +182,7 @@ static int detect_fasterrcnn(const cv::Mat& bgr, std::vector& objects) float score = 0.f; for (int i=0; i score) { label = i; @@ -197,10 +197,10 @@ static int detect_fasterrcnn(const cv::Mat& bgr, std::vector& objects) // fprintf(stderr, "%d = %f\n", label, score); // unscale to image size - float x1 = roi.data[0] / scale; - float y1 = roi.data[1] / scale; - float x2 = roi.data[2] / scale; - float y2 = roi.data[3] / scale; + float x1 = roi[0] / scale; + float y1 = roi[1] / scale; + float x2 = roi[2] / scale; + float y2 = roi[3] / scale; float pb_w = x2 - x1 + 1; float pb_h = y2 - y1 + 1; diff --git a/examples/squeezenet.cpp b/examples/squeezenet.cpp index bab2a35ba..918ec3728 100644 --- a/examples/squeezenet.cpp +++ b/examples/squeezenet.cpp @@ -42,7 +42,7 @@ static int detect_squeezenet(const cv::Mat& bgr, std::vector& cls_scores) cls_scores.resize(out.c); for (int j=0; j& bottom_blobs, std::vector& t int channels = bottom_blob.c; int q = 0; - const int* slices_ptr = (const int*)slices.data; + const int* slices_ptr = slices; for (size_t i=0; i& bottom_blobs, std::vector& t int size = bottom_blob.cstep * slice; const float* ptr = bottom_blob.channel(q); - float* outptr = top_blob.data; + float* outptr = top_blob; #if __ARM_NEON int nn = size >> 3; diff --git a/src/layer/batchnorm.cpp b/src/layer/batchnorm.cpp index ce945e2ca..c16d663e6 100644 --- a/src/layer/batchnorm.cpp +++ b/src/layer/batchnorm.cpp @@ -56,17 +56,12 @@ int BatchNorm::load_model(const ModelBin& mb) b_data.create(channels); if (b_data.empty()) return -100; - const float* slope_data_ptr = slope_data; - const float* mean_data_ptr = mean_data; - const float* var_data_ptr = var_data; - const float* bias_data_ptr = bias_data; - float* a_data_ptr = a_data; - float* b_data_ptr = b_data; + for (int i=0; i& bottom_blobs, std::vector confidence_threshold) { diff --git a/src/layer/eltwise.cpp b/src/layer/eltwise.cpp index dad8367e1..243bc89bd 100644 --- a/src/layer/eltwise.cpp +++ b/src/layer/eltwise.cpp @@ -114,12 +114,10 @@ int Eltwise::forward(const std::vector& bottom_blobs, std::vector& top } else { - const float* coeffs_ptr = coeffs; - // first blob const Mat& bottom_blob1 = bottom_blobs[1]; - float coeff0 = coeffs_ptr[0]; - float coeff1 = coeffs_ptr[1]; + float coeff0 = coeffs[0]; + float coeff1 = coeffs[1]; #pragma omp parallel for for (int q=0; q& bottom_blobs, std::vector& top for (size_t b=2; b& bottom_blobs, std::vector& top_bl // 0 otherwise // calculate hidden // gate_input_t := W_hc * h_conted_{t-1} + W_xc * x_t + b_c - const float cont = cont_blob.data[t]; + const float cont = cont_blob[t]; const Mat x = input_blob.channel(t); float* hidden_data = hidden; for (int q=0; q& bottom_blobs, std::vector& top_bl float h_cont = cont ? hidden_data[q] : 0.f; const float* x_data = x; - const float* bias_c_data_ptr = bias_c_data.data + 4 * q; - float* gates_data = gates.data + 4 * q; + const float* bias_c_data_ptr = (const float*)bias_c_data + 4 * q; + float* gates_data = (float*)gates + 4 * q; // gate I F O G - const float* weight_hc_data_I = weight_hc_data.data + weight_hc_data.w * q; - const float* weight_xc_data_I = weight_xc_data.data + weight_xc_data.w * q; - const float* weight_hc_data_F = weight_hc_data.data + weight_hc_data.w * q + size; - const float* weight_xc_data_F = weight_xc_data.data + weight_xc_data.w * q + size; - const float* weight_hc_data_O = weight_hc_data.data + weight_hc_data.w * q + size*2; - const float* weight_xc_data_O = weight_xc_data.data + weight_xc_data.w * q + size*2; - const float* weight_hc_data_G = weight_hc_data.data + weight_hc_data.w * q + size*3; - const float* weight_xc_data_G = weight_xc_data.data + weight_xc_data.w * q + size*3; + const float* weight_hc_data_I = (const float*)weight_hc_data + weight_hc_data.w * q; + const float* weight_xc_data_I = (const float*)weight_xc_data + weight_xc_data.w * q; + const float* weight_hc_data_F = (const float*)weight_hc_data + weight_hc_data.w * q + size; + const float* weight_xc_data_F = (const float*)weight_xc_data + weight_xc_data.w * q + size; + const float* weight_hc_data_O = (const float*)weight_hc_data + weight_hc_data.w * q + size*2; + const float* weight_xc_data_O = (const float*)weight_xc_data + weight_xc_data.w * q + size*2; + const float* weight_hc_data_G = (const float*)weight_hc_data + weight_hc_data.w * q + size*3; + const float* weight_xc_data_G = (const float*)weight_xc_data + weight_xc_data.w * q + size*3; float I = bias_c_data_ptr[0]; float F = bias_c_data_ptr[1]; @@ -148,7 +148,7 @@ int LSTM::forward(const std::vector& bottom_blobs, std::vector& top_bl float* output_data = output; for (int q=0; q 1 ? slope_data_ptr[q] : slope_data_ptr[0]; + float slope = num_slope > 1 ? slope_data[q] : slope_data[0]; for (int i=0; i& bottom_blobs, std::vector& to #pragma omp parallel for for (int i = 0; i < h; i++) { - float* box = top_blob.data + i * w * num_prior * 4; + float* box = (float*)top_blob + i * w * num_prior * 4; float center_x = offset * step_w; float center_y = offset * step_h + i * step_h; @@ -91,7 +91,7 @@ int PriorBox::forward(const std::vector& bottom_blobs, std::vector& to for (int k = 0; k < num_min_size; k++) { - float min_size = min_sizes.data[k]; + float min_size = min_sizes[k]; // min size box box_w = box_h = min_size; @@ -105,7 +105,7 @@ int PriorBox::forward(const std::vector& bottom_blobs, std::vector& to if (num_max_size > 0) { - float max_size = max_sizes.data[k]; + float max_size = max_sizes[k]; // max size box box_w = box_h = sqrt(min_size * max_size); diff --git a/src/layer/proposal.cpp b/src/layer/proposal.cpp index c336c90b9..11161e5f5 100644 --- a/src/layer/proposal.cpp +++ b/src/layer/proposal.cpp @@ -28,14 +28,14 @@ Proposal::Proposal() // TODO load from param ratios.create(3); - ratios.data[0] = 0.5f; - ratios.data[1] = 1.f; - ratios.data[2] = 2.f; + ratios[0] = 0.5f; + ratios[1] = 1.f; + ratios[2] = 2.f; scales.create(3); - scales.data[0] = 8.f; - scales.data[1] = 16.f; - scales.data[2] = 32.f; + scales[0] = 8.f; + scales[1] = 16.f; + scales[2] = 32.f; } static Mat generate_anchors(int base_size, const Mat& ratios, const Mat& scales) @@ -51,14 +51,14 @@ static Mat generate_anchors(int base_size, const Mat& ratios, const Mat& scales) for (int i = 0; i < num_ratio; i++) { - float ar = ratios.data[i]; + float ar = ratios[i]; int r_w = round(base_size / sqrt(ar)); int r_h = round(r_w * ar);//round(base_size * sqrt(ar)); for (int j = 0; j < num_scale; j++) { - float scale = scales.data[j]; + float scale = scales[j]; float rs_w = r_w * scale; float rs_h = r_h * scale; @@ -269,8 +269,8 @@ int Proposal::forward(const std::vector& bottom_blobs, std::vector& to } // clip predicted boxes to image - float im_w = im_info_blob.data[1]; - float im_h = im_info_blob.data[0]; + float im_w = im_info_blob[1]; + float im_h = im_info_blob[0]; #pragma omp parallel for for (int q=0; q& bottom_blobs, std::vector& to std::vector proposal_boxes; std::vector scores; - float im_scale = im_info_blob.data[2]; + float im_scale = im_info_blob[2]; float min_boxsize = min_size * im_scale; for (int q=0; q& bottom_blobs, std::vector& top_blo // 0 otherwise // calculate hidden // h_t = tanh( W_hh * h_cont_{t-1} + W_xh * x_t + b_h ) - const float cont = cont_blob.data[t]; + const float cont = cont_blob[t]; const Mat x = input_blob.channel(t); float* hidden_data = hidden; for (int q=0; q& bottom_blobs, std::vector& top_blo float* output_data = output; for (int q=0; q& bottom_top_blobs) const if (bias_term) { - const float* bias_ptr = bias_data; #pragma omp parallel for for (int q=0; q& bottom_blobs, std::vector& top_b int channels = bottom_blob.c; int q = 0; - const int* slices_ptr = (const int*)slices.data; + const int* slices_ptr = slices; for (size_t i=0; i& bottom_blobs, std::vector& top_b int size = bottom_blob.cstep * slice; const float* ptr = bottom_blob.channel(q); - float* outptr = top_blob.data; - for (int j=0; j> 2 : 0; @@ -324,8 +324,8 @@ static void copy_make_border_image(const Mat& src, Mat& dst, int top, int left, int w = dst.w; int h = dst.h; - const float* ptr = src.data; - float* outptr = dst.data; + const float* ptr = src;//.data; + float* outptr = dst;//.data; if (type == BORDER_CONSTANT) { @@ -508,8 +508,8 @@ static void copy_cut_border_image(const Mat& src, Mat& dst, int top, int left) int w = dst.w; int h = dst.h; - const float* ptr = src.data + src.w * top + left; - float* outptr = dst.data; + const float* ptr = src.row(top) + left;//.data + src.w * top + left; + float* outptr = dst;//.data; for (int y = 0; y < h; y++) { diff --git a/src/mat.h b/src/mat.h index 734e26b03..2b32988cb 100644 --- a/src/mat.h +++ b/src/mat.h @@ -30,25 +30,26 @@ public: // empty Mat(); // vec - Mat(int w); + Mat(int w, size_t elemsize = 4); // image - Mat(int w, int h); + Mat(int w, int h, size_t elemsize = 4); // dim - Mat(int w, int h, int c); + Mat(int w, int h, int c, size_t elemsize = 4); // copy Mat(const Mat& m); // external vec - Mat(int w, float* data); + Mat(int w, void* data, size_t elemsize = 4); // external image - Mat(int w, int h, float* data); + Mat(int w, int h, void* data, size_t elemsize = 4); // external dim - Mat(int w, int h, int c, float* data); + Mat(int w, int h, int c, void* data, size_t elemsize = 4); // release ~Mat(); // assign Mat& operator=(const Mat& m); // set all void fill(float v); + template void fill(T v); // deep copy Mat clone() const; // reshape vec @@ -58,11 +59,11 @@ public: // reshape dim Mat reshape(int w, int h, int c) const; // allocate vec - void create(int w); + void create(int w, size_t elemsize = 4); // allocate image - void create(int w, int h); + void create(int w, int h, size_t elemsize = 4); // allocate dim - void create(int w, int h, int c); + void create(int w, int h, int c, size_t elemsize = 4); // refcount++ void addref(); // refcount-- @@ -76,8 +77,16 @@ public: const Mat channel(int c) const; float* row(int y); const float* row(int y) const; - operator float*(); - operator const float*() const; + template T* row(int y); + template const T* row(int y) const; + + // access raw data + template operator T*(); + template operator const T*() const; + + // convenient access float vec element + float& operator[](int i); + const float& operator[](int i) const; enum { @@ -119,15 +128,23 @@ public: // convenient construct from half precisoin floating point data static Mat from_float16(const unsigned short* data, int size); - // the dimensionality - int dims; // pointer to the data - float* data; + void* data; - // pointer to the reference counter; + // pointer to the reference counter // when points to user-allocated data, the pointer is NULL int* refcount; + // element size in bytes + // 4 = float32/int32 + // 2 = float16 + // 1 = int8/uint8 + // 0 = empty + size_t elemsize; + + // the dimensionality + int dims; + int w; int h; int c; @@ -217,30 +234,30 @@ static inline void NCNN_XADD(int* addr, int delta) { int tmp = *addr; *addr += d #endif inline Mat::Mat() - : dims(0), data(0), refcount(0), w(0), h(0), c(0), cstep(0) + : data(0), refcount(0), elemsize(0), dims(0), w(0), h(0), c(0), cstep(0) { } -inline Mat::Mat(int _w) - : dims(0), data(0), refcount(0) +inline Mat::Mat(int _w, size_t _elemsize) + : data(0), refcount(0), dims(0) { - create(_w); + create(_w, _elemsize); } -inline Mat::Mat(int _w, int _h) - : dims(0), data(0), refcount(0) +inline Mat::Mat(int _w, int _h, size_t _elemsize) + : data(0), refcount(0), dims(0) { - create(_w, _h); + create(_w, _h, _elemsize); } -inline Mat::Mat(int _w, int _h, int _c) - : dims(0), data(0), refcount(0) +inline Mat::Mat(int _w, int _h, int _c, size_t _elemsize) + : data(0), refcount(0), dims(0) { - create(_w, _h, _c); + create(_w, _h, _c, _elemsize); } inline Mat::Mat(const Mat& m) - : dims(m.dims), data(m.data), refcount(m.refcount) + : data(m.data), refcount(m.refcount), elemsize(m.elemsize), dims(m.dims) { if (refcount) NCNN_XADD(refcount, 1); @@ -252,8 +269,8 @@ inline Mat::Mat(const Mat& m) cstep = m.cstep; } -inline Mat::Mat(int _w, float* _data) - : dims(1), data(_data), refcount(0) +inline Mat::Mat(int _w, void* _data, size_t _elemsize) + : data(_data), refcount(0), elemsize(_elemsize), dims(1) { w = _w; h = 1; @@ -262,8 +279,8 @@ inline Mat::Mat(int _w, float* _data) cstep = w; } -inline Mat::Mat(int _w, int _h, float* _data) - : dims(2), data(_data), refcount(0) +inline Mat::Mat(int _w, int _h, void* _data, size_t _elemsize) + : data(_data), refcount(0), elemsize(_elemsize), dims(2) { w = _w; h = _h; @@ -272,14 +289,14 @@ inline Mat::Mat(int _w, int _h, float* _data) cstep = w * h; } -inline Mat::Mat(int _w, int _h, int _c, float* _data) - : dims(3), data(_data), refcount(0) +inline Mat::Mat(int _w, int _h, int _c, void* _data, size_t _elemsize) + : data(_data), refcount(0), elemsize(_elemsize), dims(3) { w = _w; h = _h; c = _c; - cstep = alignSize(w * h * sizeof(float), 16) >> 2; + cstep = alignSize(w * h * elemsize, 16) / elemsize; } inline Mat::~Mat() @@ -297,10 +314,11 @@ inline Mat& Mat::operator=(const Mat& m) release(); - dims = m.dims; data = m.data; refcount = m.refcount; + elemsize = m.elemsize; + dims = m.dims; w = m.w; h = m.h; c = m.c; @@ -313,7 +331,7 @@ inline Mat& Mat::operator=(const Mat& m) inline void Mat::fill(float _v) { int size = total(); - float* ptr = data; + float* ptr = (float*)data; #if __ARM_NEON int nn = size >> 2; @@ -354,6 +372,17 @@ inline void Mat::fill(float _v) } } +template +inline void Mat::fill(T _v) +{ + int size = total(); + T* ptr = (T*)data; + for (int i=0; i 0) { - memcpy(m.data, data, total() * sizeof(float)); + memcpy(m.data, data, total() * elemsize); } return m; @@ -383,14 +412,14 @@ inline Mat Mat::reshape(int _w) const if (dims == 3 && cstep != (size_t)w * h) { Mat m; - m.create(_w); + m.create(_w, elemsize); // flatten for (int i=0; i> 2) + if ((size_t)_w * _h != alignSize(_w * _h * elemsize, 16) / elemsize) { Mat m; - m.create(_w, _h, _c); + m.create(_w, _h, _c, elemsize); // align channel for (int i=0; i<_c; i++) { - const float* ptr = data + i * _w * _h; - float* mptr = m.data + i * m.cstep; - memcpy(mptr, ptr, _w * _h * sizeof(float)); + const void* ptr = (unsigned char*)data + i * _w * _h * elemsize; + void* mptr = (unsigned char*)m.data + i * m.cstep * m.elemsize; + memcpy(mptr, ptr, _w * _h * elemsize); } return m; @@ -476,22 +503,22 @@ inline Mat Mat::reshape(int _w, int _h, int _c) const Mat m = *this; m.dims = 3; - m.w = _w; m.h = _h; m.c = _c; - m.cstep = alignSize(_w * _h * sizeof(float), 16) >> 2; + m.cstep = alignSize(_w * _h * elemsize, 16) / elemsize; return m; } -inline void Mat::create(int _w) +inline void Mat::create(int _w, size_t _elemsize) { release(); - dims = 1; + elemsize = _elemsize; + dims = 1; w = _w; h = 1; c = 1; @@ -500,19 +527,20 @@ inline void Mat::create(int _w) if (total() > 0) { - size_t totalsize = total() * sizeof(float); - data = (float*)fastMalloc(totalsize + (int)sizeof(*refcount)); + size_t totalsize = total() * elemsize; + data = fastMalloc(totalsize + (int)sizeof(*refcount)); refcount = (int*)(((unsigned char*)data) + totalsize); *refcount = 1; } } -inline void Mat::create(int _w, int _h) +inline void Mat::create(int _w, int _h, size_t _elemsize) { release(); - dims = 2; + elemsize = _elemsize; + dims = 2; w = _w; h = _h; c = 1; @@ -521,29 +549,30 @@ inline void Mat::create(int _w, int _h) if (total() > 0) { - size_t totalsize = total() * sizeof(float); - data = (float*)fastMalloc(totalsize + (int)sizeof(*refcount)); + size_t totalsize = total() * elemsize; + data = fastMalloc(totalsize + (int)sizeof(*refcount)); refcount = (int*)(((unsigned char*)data) + totalsize); *refcount = 1; } } -inline void Mat::create(int _w, int _h, int _c) +inline void Mat::create(int _w, int _h, int _c, size_t _elemsize) { release(); - dims = 3; + elemsize = _elemsize; + dims = 3; w = _w; h = _h; c = _c; - cstep = alignSize(w * h * sizeof(float), 16) >> 2; + cstep = alignSize(w * h * elemsize, 16) / elemsize; if (total() > 0) { - size_t totalsize = total() * sizeof(float); - data = (float*)fastMalloc(totalsize + (int)sizeof(*refcount)); + size_t totalsize = total() * elemsize; + data = fastMalloc(totalsize + (int)sizeof(*refcount)); refcount = (int*)(((unsigned char*)data) + totalsize); *refcount = 1; } @@ -560,9 +589,11 @@ inline void Mat::release() if (refcount && NCNN_XADD(refcount, -1) == 1) fastFree(data); - dims = 0; data = 0; + elemsize = 0; + + dims = 0; w = 0; h = 0; c = 0; @@ -584,32 +615,56 @@ inline size_t Mat::total() const inline Mat Mat::channel(int c) { - return Mat(w, h, data + cstep * c); + return Mat(w, h, (unsigned char*)data + cstep * c * elemsize, elemsize); } inline const Mat Mat::channel(int c) const { - return Mat(w, h, data + cstep * c); + return Mat(w, h, (unsigned char*)data + cstep * c * elemsize, elemsize); } inline float* Mat::row(int y) { - return data + w * y; + return (float*)data + w * y; } inline const float* Mat::row(int y) const { - return data + w * y; + return (const float*)data + w * y; +} + +template +inline T* Mat::row(int y) +{ + return (T*)data + w * y; +} + +template +inline const T* Mat::row(int y) const +{ + return (const T*)data + w * y; +} + +template +inline Mat::operator T*() +{ + return (T*)data; +} + +template +inline Mat::operator const T*() const +{ + return (const T*)data; } -inline Mat::operator float*() +inline float& Mat::operator[](int i) { - return data; + return ((float*)data)[i]; } -inline Mat::operator const float*() const +inline const float& Mat::operator[](int i) const { - return data; + return ((const float*)data)[i]; } } // namespace ncnn diff --git a/src/paramdict.cpp b/src/paramdict.cpp index 26f10f92e..cd9a0c0f6 100644 --- a/src/paramdict.cpp +++ b/src/paramdict.cpp @@ -121,9 +121,15 @@ int ParamDict::load_param(FILE* fp) bool is_float = vstr_is_float(vstr); if (is_float) - nscan = sscanf(vstr, "%f", ¶ms[id].v.data[j]); + { + float* ptr = params[id].v; + nscan = sscanf(vstr, "%f", &ptr[j]); + } else - nscan = sscanf(vstr, "%d", (int*)¶ms[id].v.data[j]); + { + int* ptr = params[id].v; + nscan = sscanf(vstr, "%d", &ptr[j]); + } if (nscan != 1) { fprintf(stderr, "ParamDict parse array element fail\n"); @@ -196,10 +202,8 @@ int ParamDict::load_param_bin(FILE* fp) params[id].v.create(len); - for (int j = 0; j < len; j++) - { - fread(¶ms[id].v.data[j], sizeof(float), 1, fp); - } + float* ptr = params[id].v; + fread(ptr, sizeof(float), len, fp); } else { @@ -237,11 +241,8 @@ int ParamDict::load_param(const unsigned char*& mem) params[id].v.create(len); - for (int j = 0; j < len; j++) - { - params[id].v.data[j] = *(float*)(mem); - mem += 4; - } + memcpy(params[id].v.data, mem, len * 4); + mem += 4; } else {